From 750f07880d90480025c080f968cec6106ccb4f3e Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 7 Aug 2024 15:18:24 +0300 Subject: [PATCH 001/334] refactor: optimization --- src/core/component/meta/method.ts | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index db02152931..881167da5e 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -17,10 +17,6 @@ import type { ComponentMeta } from 'core/component/interface'; * @param [constructor] */ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = meta.constructor): void { - const - proto = constructor.prototype, - ownProps = Object.getOwnPropertyNames(proto); - const { componentName: src, props, @@ -31,22 +27,18 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me methods } = meta; - ownProps.forEach((name) => { - if (name === 'constructor') { - return; - } - - const - desc = Object.getOwnPropertyDescriptor(proto, name); + const + proto = constructor.prototype, + descriptors = Object.getOwnPropertyDescriptors(proto); - if (desc == null) { + Object.entries(descriptors).forEach(([name, desc]) => { + if (name === 'constructor') { return; } // Methods if ('value' in desc) { - const - fn = desc.value; + const fn = desc.value; if (!Object.isFunction(fn)) { return; @@ -60,13 +52,12 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me propKey = `${name}Prop`, storeKey = `${name}Store`; - let - metaKey: string; + let metaKey: string; // Computed fields are cached by default if ( name in computedFields || - !(name in accessors) && (props[propKey] || fields[storeKey] || systemFields[storeKey]) + !(name in accessors) && (props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) ) { metaKey = 'computedFields'; @@ -74,8 +65,7 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me metaKey = 'accessors'; } - let - field: Dictionary; + let field: Dictionary; if (props[name] != null) { field = props; From 2ecf0c9c68f702e88b760fe5467295e7e7a1fd51 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 7 Aug 2024 16:55:47 +0300 Subject: [PATCH 002/334] refactor: optimization --- src/components/friends/sync/link.ts | 4 +- src/core/component/decorators/factory.ts | 6 +- src/core/component/directives/attrs/index.ts | 4 +- src/core/component/event/emitter.ts | 10 +- src/core/component/meta/fill.ts | 104 +++++++++--------- src/core/component/prop/helpers.ts | 6 +- src/core/component/reflect/README.md | 4 +- src/core/component/reflect/const.ts | 47 ++++++-- src/core/component/reflect/constructor.ts | 14 +-- src/core/component/reflect/mod.ts | 5 +- src/core/component/reflect/property.ts | 25 +++-- src/core/component/reflect/validators.ts | 20 +++- .../component/render/helpers/normalizers.ts | 8 +- src/core/component/watch/create.ts | 6 +- 14 files changed, 153 insertions(+), 110 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 1b0d8ad1cf..361680fd73 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -7,7 +7,7 @@ */ import { isProxy } from 'core/object/watch'; -import { bindingRgxp, customWatcherRgxp, getPropertyInfo } from 'core/component'; +import { getPropertyInfo, isBinding, customWatcherRgxp } from 'core/component'; import type iBlock from 'components/super/i-block/i-block'; import type Sync from 'components/friends/sync/class'; @@ -279,7 +279,7 @@ export function link( resolvedOpts: AsyncWatchOptions = {}; if (path == null) { - resolvedPath = `${destPath.replace(bindingRgxp, '')}Prop`; + resolvedPath = `${isBinding.test(destPath) ? isBinding.replace(destPath) : destPath}Prop`; } else if (Object.isString(path) || isProxy(path) || 'ctx' in path) { resolvedPath = path; diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index 4321e64f8f..c5e405eaeb 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -8,7 +8,7 @@ import { defProp } from 'core/const/props'; -import { storeRgxp } from 'core/component/reflect'; +import { isStore } from 'core/component/reflect'; import { initEmitter } from 'core/component/event'; import { metaPointers } from 'core/component/const'; @@ -258,8 +258,8 @@ export function paramsFactory( }; } - if (tiedFieldMap[metaKey] != null && RegExp.test(storeRgxp, key)) { - meta.tiedFields[key] = key.replace(storeRgxp, ''); + if (tiedFieldMap[metaKey] != null && isStore.test(key)) { + meta.tiedFields[key] = isStore.replace(key); } function inheritFromParent() { diff --git a/src/core/component/directives/attrs/index.ts b/src/core/component/directives/attrs/index.ts index 4fe3999535..fb7bd444e5 100644 --- a/src/core/component/directives/attrs/index.ts +++ b/src/core/component/directives/attrs/index.ts @@ -12,7 +12,7 @@ */ import { components } from 'core/component/const'; -import { propGetterRgxp } from 'core/component/reflect'; +import { isPropGetter } from 'core/component/reflect'; import { ComponentEngine, DirectiveBinding, VNode } from 'core/component/engines'; import { setVNodePatchFlags, normalizeComponentAttrs } from 'core/component/render'; @@ -314,7 +314,7 @@ ComponentEngine.directive('attrs', { // For example, `@:value = createPropAccessors(() => someValue)`. // A distinctive feature of such events is the prefix `@:` or `on:`. // Such events are processed in a special way. - const isSystemGetter = propGetterRgxp.test(event); + const isSystemGetter = isPropGetter.test(event); props[event] = attrVal; if (!isSystemGetter) { diff --git a/src/core/component/event/emitter.ts b/src/core/component/event/emitter.ts index d1b8d01d86..223de3e507 100644 --- a/src/core/component/event/emitter.ts +++ b/src/core/component/event/emitter.ts @@ -52,17 +52,15 @@ export const initEmitter = new EventEmitter({ listener: ListenerFn, opts?: true | OnOptions ): EventEmitter { - Array.concat([], event).forEach((event) => { - const - chunks = event.split('.', 2); + (Object.isArray(event) ? event : [event]).forEach((event) => { + const chunks = event.split('.', 2); if (chunks[0] === 'constructor') { initEventOnce(event, listener, opts); - const - p = componentParams.get(chunks[1]); + const p = componentParams.get(chunks[1]); - if (p && Object.isPlainObject(p.functional)) { + if (p != null && Object.isDictionary(p.functional)) { initEventOnce(`${event}-functional`, listener, opts); } diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 64f81bf178..ecbc39211b 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -9,7 +9,7 @@ import { DEFAULT_WRAPPER } from 'core/component/const'; import { getComponentContext } from 'core/component/context'; -import { isAbstractComponent, bindingRgxp } from 'core/component/reflect'; +import { isAbstractComponent, isBinding } from 'core/component/reflect'; import { isTypeCanBeFunc } from 'core/component/prop'; import { addMethodsToMeta } from 'core/component/meta/method'; @@ -22,12 +22,13 @@ import type { ComponentConstructor, ComponentMeta, ModVal } from 'core/component * @param meta * @param [constructor] - the component constructor */ -export function fillMeta( - meta: ComponentMeta, - constructor: ComponentConstructor = meta.constructor -): ComponentMeta { +export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor = meta.constructor): ComponentMeta { addMethodsToMeta(meta, constructor); + if (isAbstractComponent.test(meta.componentName)) { + return meta; + } + const { component, params, @@ -43,12 +44,17 @@ export function fillMeta( watchPropDependencies } = meta; - const instance = Object.cast(new constructor()); - meta.instance = instance; + let instance: unknown = null; - if (isAbstractComponent.test(meta.componentName)) { - return meta; - } + Object.defineProperty(meta, 'instance', { + enumerable: true, + configurable: true, + + get() { + instance ??= new constructor(); + return instance; + } + }); const isRoot = params.root === true, @@ -70,7 +76,7 @@ export function fillMeta( skipDefault = true; if (defaultProps || prop.forceDefault) { - const defaultInstanceValue = instance[name]; + const defaultInstanceValue = meta.instance[name]; skipDefault = false; getDefault = defaultInstanceValue; @@ -123,31 +129,28 @@ export function fillMeta( } if (canWatchProps) { - const - normalizedKey = name.replace(bindingRgxp, ''); + const normalizedName = isBinding.test(name) ? isBinding.replace(name) : name; - if ((computedFields[normalizedKey] ?? accessors[normalizedKey]) != null) { - const - props = watchPropDependencies.get(normalizedKey) ?? new Set(); + if ((computedFields[normalizedName] ?? accessors[normalizedName]) != null) { + const props = watchPropDependencies.get(normalizedName) ?? new Set(); props.add(name); - watchPropDependencies.set(normalizedKey, props); + watchPropDependencies.set(normalizedName, props); } else { watchDependencies.forEach((deps, path) => { - for (let i = 0; i < deps.length; i++) { - const - dep = deps[i]; - - if ((Object.isArray(dep) ? dep : dep.split('.'))[0] === name) { - const - props = watchPropDependencies.get(path) ?? new Set(); + deps.some((dep) => { + if ((Object.isArray(dep) ? dep : dep.split('.', 1))[0] === name) { + const props = watchPropDependencies.get(path) ?? new Set(); props.add(name); watchPropDependencies.set(path, props); - break; + + return true; } - } + + return false; + }); }); } } @@ -156,16 +159,15 @@ export function fillMeta( // Fields [meta.systemFields, meta.fields].forEach((field) => { - Object.entries(field).forEach(([key, field]) => { + Object.entries(field).forEach(([name, field]) => { field?.watchers?.forEach((watcher) => { if (isFunctional && watcher.functional === false) { return; } - const - watcherListeners = watchers[key] ?? []; + const watcherListeners = watchers[name] ?? []; - watchers[key] = watcherListeners; + watchers[name] = watcherListeners; watcherListeners.push(watcher); }); }); @@ -192,21 +194,19 @@ export function fillMeta( } component.methods[name] = wrapper; - Object.defineProperty(wrapper, 'length', {get: () => method.fn.length}); - function wrapper(this: object) { - // eslint-disable-next-line prefer-rest-params - return method!.fn.apply(getComponentContext(this), arguments); + if (wrapper.length !== method.fn.length) { + Object.defineProperty(wrapper, 'length', {get: () => method.fn.length}); } if (method.watchers != null) { - Object.entries(method.watchers).forEach(([key, watcher]) => { + Object.entries(method.watchers).forEach(([name, watcher]) => { if (watcher == null || isFunctional && watcher.functional === false) { return; } - const watcherListeners = watchers[key] ?? []; - watchers[key] = watcherListeners; + const watcherListeners = watchers[name] ?? []; + watchers[name] = watcherListeners; watcherListeners.push({ ...watcher, @@ -220,36 +220,38 @@ export function fillMeta( // Method hooks if (method.hooks) { - Object.entries(method.hooks).forEach(([key, hook]) => { + Object.entries(method.hooks).forEach(([name, hook]) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (hook == null || isFunctional && hook.functional === false) { return; } - hooks[key].push({...hook, fn: method.fn}); + hooks[name].push({...hook, fn: method.fn}); }); } + + function wrapper(this: object) { + // eslint-disable-next-line prefer-rest-params + return method!.fn.apply(getComponentContext(this), arguments); + } }); // Modifiers - const - {mods} = component; + const {mods} = component; Object.entries(meta.mods).forEach(([key, mod]) => { - let - defaultValue: CanUndef; + let defaultValue: CanUndef; if (mod != null) { - for (let i = 0; i < mod.length; i++) { - const - el = mod[i]; - - if (Object.isArray(el)) { - defaultValue = el; - break; + mod.some((val) => { + if (Object.isArray(val)) { + defaultValue = val; + return true; } - } + + return false; + }); mods[key] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; } diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index 7353214729..51d364167e 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { propGetterRgxp } from 'core/component/reflect'; +import { isPropGetter } from 'core/component/reflect'; import type { ComponentInterface } from 'core/component/interface'; /** @@ -50,11 +50,11 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { } } - if (!attrName.startsWith(propPrefix)) { + if (!isPropGetter.test(attrName)) { return; } - const propName = attrName.replace(propGetterRgxp, ''); + const propName = isPropGetter.replace(attrName); if (meta.props[propName]?.forceUpdate === false) { propValuesToUpdate.push([propName, attrName]); diff --git a/src/core/component/reflect/README.md b/src/core/component/reflect/README.md index 0e386ed57f..66e63b2b48 100644 --- a/src/core/component/reflect/README.md +++ b/src/core/component/reflect/README.md @@ -29,11 +29,11 @@ console.log(getComponentMods(getInfoFromConstructor(bButton))); #### isSmartComponent -This regular expression can be used to determine whether a component is a "smart" component based on its name. +This API can be used to determine whether a component is a "smart" component based on its name. #### isAbstractComponent -This regular expression allows you to determine if a component is abstract based on its name. +This API allows you to determine if a component is abstract based on its name. ### Functions diff --git a/src/core/component/reflect/const.ts b/src/core/component/reflect/const.ts index 30968c5fed..ea2d94025d 100644 --- a/src/core/component/reflect/const.ts +++ b/src/core/component/reflect/const.ts @@ -6,14 +6,45 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -export const - propRgxp = /Prop$|^\$props/, - propGetterRgxp = /^(?:@|on):/, - privateFieldRgxp = /^\[\[(.*)]]$/, - storeRgxp = /Store$/, - attrRgxp = /^\$attrs/, - bindingRgxp = /(?:Prop|Store)$/, - hasSeparator = /\./; +export const isPropGetter = { + test(name: string): boolean { + return name.startsWith('@:') || name.startsWith('on:'); + }, + + replace(name: string): string { + return name.startsWith('@') ? name.slice('@:'.length) : name.slice('on:'.length); + } +}; + +export const isPrivateField = { + test(name: string): boolean { + return name.startsWith('[['); + }, + + replace(name: string): string { + return name.slice('[['.length, name.length - ']]'.length); + } +}; + +export const isStore = { + test(name: string): boolean { + return name.endsWith('Store'); + }, + + replace(name: string): string { + return name.slice(0, name.length - 'Store'.length); + } +}; + +export const isBinding = { + test(name: string): boolean { + return isStore.test(name) || name.endsWith('Prop'); + }, + + replace(name: string): string { + return name.startsWith('p') ? name.slice('Prop'.length) : name.slice('Store'.length); + } +}; export const dsComponentsMods = (() => { try { diff --git a/src/core/component/reflect/constructor.ts b/src/core/component/reflect/constructor.ts index 7a17e3a96d..8eb9339ddd 100644 --- a/src/core/component/reflect/constructor.ts +++ b/src/core/component/reflect/constructor.ts @@ -28,8 +28,7 @@ import type { ComponentConstructorInfo } from 'core/component/reflect/interface' * ``` */ export function getComponentName(constructor: Function): string { - const - nm = constructor.name; + const nm = constructor.name; if (Object.isString(nm)) { return nm.dasherize(); @@ -95,10 +94,9 @@ export function getInfoFromConstructor( // Mix the "functional" parameter from the parent @component declaration if (parentParams) { - let - functional: typeof params.functional; + let functional: typeof params.functional; - if (Object.isPlainObject(params.functional) && Object.isPlainObject(parentParams.functional)) { + if (Object.isDictionary(params.functional) && Object.isDictionary(parentParams.functional)) { functional = {...parentParams.functional, ...params.functional}; } else { @@ -114,16 +112,18 @@ export function getInfoFromConstructor( componentParams.set(name, params); } + const isSmart = name.endsWith('-functional'); + return { name, layer, - componentName: name.replace(isSmartComponent, ''), + componentName: isSmart ? isSmartComponent.replace(name) : name, constructor, params, isAbstract: isAbstractComponent.test(name), - isSmart: isSmartComponent.test(name), + isSmart, parent, parentParams, diff --git a/src/core/component/reflect/mod.ts b/src/core/component/reflect/mod.ts index fa12cd9651..6942d3b418 100644 --- a/src/core/component/reflect/mod.ts +++ b/src/core/component/reflect/mod.ts @@ -61,8 +61,7 @@ export function getComponentMods(component: ComponentConstructorInfo): ModsDecl if (modDecl != null && modDecl.length > 0) { const cache = new Map(); - let - active: CanUndef; + let active: CanUndef; modDecl.forEach((modVal) => { if (Object.isArray(modVal)) { @@ -74,7 +73,7 @@ export function getComponentMods(component: ComponentConstructorInfo): ModsDecl cache.set(active, [active]); } else { - const normalizedModVal = Object.isPlainObject(modVal) ? + const normalizedModVal = Object.isDictionary(modVal) ? modVal : String(modVal); diff --git a/src/core/component/reflect/property.ts b/src/core/component/reflect/property.ts index 4d74478e9d..3cfbc7e949 100644 --- a/src/core/component/reflect/property.ts +++ b/src/core/component/reflect/property.ts @@ -9,7 +9,7 @@ import { deprecate } from 'core/functools/deprecation'; import { ComponentInterface } from 'core/component/interface'; -import { propRgxp, attrRgxp, privateFieldRgxp, storeRgxp, hasSeparator } from 'core/component/reflect/const'; +import { isStore, isPrivateField } from 'core/component/reflect/const'; import type { PropertyInfo, AccessorType } from 'core/component/reflect/interface'; /** @@ -59,24 +59,25 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr chunks: Nullable, rootI = 0; - if (hasSeparator.test(path)) { + if (path.includes('.')) { chunks = path.split('.'); - let - obj: Nullable = component; + let obj: Nullable = component; - for (let i = 0; i < chunks.length; i++) { + chunks.some((chunk, i, chunks) => { if (obj == null) { - break; + return true; } - obj = obj[chunks[i]]; + obj = obj[chunk]; if (obj?.instance instanceof ComponentInterface) { component = obj; rootI = i === chunks.length - 1 ? i : i + 1; } - } + + return false; + }); path = chunks.slice(rootI).join('.'); topPath = chunks.slice(0, rootI + 1).join('.'); @@ -128,22 +129,22 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr originalTopPath }; - if (privateFieldRgxp.test(name)) { + if (isPrivateField.test(name)) { info.type = 'system'; return info; } - if (RegExp.test(propRgxp, name)) { + if (name.startsWith('$props') || name.endsWith('Prop')) { info.type = 'prop'; return info; } - if (RegExp.test(attrRgxp, name)) { + if (name.startsWith('$attrs')) { info.type = 'attr'; return info; } - if (RegExp.test(storeRgxp, name)) { + if (isStore.test(name)) { if (fields[name] != null) { return info; } diff --git a/src/core/component/reflect/validators.ts b/src/core/component/reflect/validators.ts index 14561d892c..8c4f525df1 100644 --- a/src/core/component/reflect/validators.ts +++ b/src/core/component/reflect/validators.ts @@ -7,11 +7,23 @@ */ /** - * This regular expression can be used to determine whether a component is a "smart" component based on its name + * This API can be used to determine whether a component is a "smart" component based on its name */ -export const isSmartComponent = /-functional$/; +export const isSmartComponent = { + replace(component: string): string { + return component.slice(0, component.length - '-functional'.length); + }, + + test(component: string): boolean { + return component.endsWith('-functional'); + } +}; /** - * This regular expression allows you to determine if a component is abstract based on its name + * This API allows you to determine if a component is abstract based on its name */ -export const isAbstractComponent = /^[iv]-/; +export const isAbstractComponent = { + test(component: string): boolean { + return component.startsWith('i-') || component.startsWith('v-'); + } +}; diff --git a/src/core/component/render/helpers/normalizers.ts b/src/core/component/render/helpers/normalizers.ts index c96b2906ec..59adb3e505 100644 --- a/src/core/component/render/helpers/normalizers.ts +++ b/src/core/component/render/helpers/normalizers.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { propGetterRgxp } from 'core/component/reflect'; +import { isPropGetter } from 'core/component/reflect'; import type { ComponentMeta } from 'core/component/meta'; /** @@ -155,12 +155,12 @@ export function normalizeComponentAttrs( const needSetAdditionalProp = functional === true && dynamicProps != null && - propGetterRgxp.test(attrName) && Object.isFunction(value); + isPropGetter.test(attrName) && Object.isFunction(value); // For correct operation in functional components, we need to additionally duplicate such props if (needSetAdditionalProp) { const - tiedPropName = attrName.replace(propGetterRgxp, ''), + tiedPropName = isPropGetter.replace(attrName), tiedPropValue = value()[0]; normalizedAttrs[tiedPropName] = tiedPropValue; @@ -168,7 +168,7 @@ export function normalizeComponentAttrs( dynamicProps.push(tiedPropName); } - if (propName in props || propName.replace(propGetterRgxp, '') in props) { + if (propName in props || isPropGetter.test(attrName) && isPropGetter.replace(attrName) in props) { changeAttrName(attrName, propName); } else { diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 5e284321c5..a3a8c193ef 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -9,7 +9,7 @@ /* eslint-disable max-lines-per-function */ import watch, { mute, unmute, unwrap, getProxyType, isProxy, WatchHandlerParams } from 'core/object/watch'; -import { getPropertyInfo, privateFieldRgxp, PropertyInfo } from 'core/component/reflect'; +import { getPropertyInfo, isPrivateField, PropertyInfo } from 'core/component/reflect'; import { tiedWatchers, watcherInitializer } from 'core/component/watch/const'; import { cloneWatchValue } from 'core/component/watch/clone'; @@ -453,14 +453,14 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface }; const externalWatchHandler = (value: unknown, oldValue: unknown, i?: WatchHandlerParams) => { - const fromSystem = i != null && Object.isString(i.path[0]) && i.path[0].startsWith('[['); + const fromSystem = i != null && Object.isString(i.path[0]) && isPrivateField.test(i.path[0]); // This situation occurs when the root observable object has changed, // and we need to remove the watchers of all its "nested parts", but leave the root watcher intact destructors.splice(1, destructors.length).forEach((destroy) => destroy()); if (fromSystem) { - i.path = [String(i.path[0]).replace(privateFieldRgxp, '$1'), ...i.path.slice(1)]; + i.path = [isPrivateField.replace(String(i.path[0])), ...i.path.slice(1)]; attachDeepProxy(false); } else { From 872cf8ad08fd34aeacf701e76b05e63c5e4422c1 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 8 Aug 2024 18:17:44 +0300 Subject: [PATCH 003/334] feat: support for partial classes & optimization --- src/components/base/b-bottom-slide/props.ts | 2 +- src/components/base/b-list/props.ts | 2 +- src/components/base/b-router/props.ts | 2 +- src/components/base/b-slider/props.ts | 2 +- src/components/base/b-tree/props.ts | 2 +- .../base/b-virtual-scroll-new/handlers.ts | 2 +- .../base/b-virtual-scroll-new/props.ts | 2 +- src/components/form/b-button/props.ts | 2 +- src/components/form/b-form/props.ts | 2 +- src/components/form/b-select/props.ts | 2 +- src/components/super/i-block/base/index.ts | 2 +- src/components/super/i-block/event/index.ts | 2 +- src/components/super/i-block/friends.ts | 2 +- src/components/super/i-block/mods/index.ts | 2 +- src/components/super/i-block/props.ts | 2 +- .../super/i-block/providers/index.ts | 2 +- src/components/super/i-block/state/index.ts | 2 +- src/components/super/i-data/data.ts | 6 +- src/components/super/i-data/handlers.ts | 6 +- src/components/super/i-input/fields.ts | 2 +- .../super/i-input/modules/handlers.ts | 2 +- src/components/super/i-input/props.ts | 2 +- src/core/component/accessor/index.ts | 3 +- src/core/component/const/cache.ts | 6 ++ .../component/decorators/component/index.ts | 68 +++++++++++++------ src/core/component/engines/vue3/component.ts | 6 +- src/core/component/init/component.ts | 9 +-- src/core/component/meta/create.ts | 2 +- src/core/component/meta/fill.ts | 9 ++- src/core/component/meta/interface/index.ts | 2 +- src/core/component/meta/interface/options.ts | 2 + src/core/component/reflect/const.ts | 8 ++- src/core/component/reflect/constructor.ts | 66 +++++++++++++++--- src/core/component/reflect/interface.ts | 6 +- .../component/render/helpers/normalizers.ts | 2 +- 35 files changed, 167 insertions(+), 74 deletions(-) diff --git a/src/components/base/b-bottom-slide/props.ts b/src/components/base/b-bottom-slide/props.ts index c64f9d963a..7db52af174 100644 --- a/src/components/base/b-bottom-slide/props.ts +++ b/src/components/base/b-bottom-slide/props.ts @@ -13,7 +13,7 @@ import iBlock, { component, prop } from 'components/super/i-block/i-block'; import { heightMode } from 'components/base/b-bottom-slide/const'; import type { HeightMode } from 'components/base/b-bottom-slide/interface'; -@component() +@component({partial: 'b-bottom-slide'}) export default abstract class iBottomSlideProps extends iBlock { /** * Component height mode: diff --git a/src/components/base/b-list/props.ts b/src/components/base/b-list/props.ts index dd3b5ef42b..29280c9720 100644 --- a/src/components/base/b-list/props.ts +++ b/src/components/base/b-list/props.ts @@ -14,7 +14,7 @@ import iData, { prop, component } from 'components/super/i-data/i-data'; import type { Item } from 'components/base/b-list/b-list'; -@component() +@component({partial: 'b-list'}) export default abstract class iListProps extends iData { /** {@link iActiveItems.Item} */ readonly Item!: Item; diff --git a/src/components/base/b-router/props.ts b/src/components/base/b-router/props.ts index e777b76ac8..9fc6b91ebb 100644 --- a/src/components/base/b-router/props.ts +++ b/src/components/base/b-router/props.ts @@ -14,7 +14,7 @@ import iData, { component, prop } from 'components/super/i-data/i-data'; import type { StaticRoutes, RouteOption } from 'components/base/b-router/interface'; import type bRouter from 'components/base/b-router/b-router'; -@component() +@component({partial: 'b-router'}) export default abstract class iRouterProps extends iData { /** * Type: page parameters diff --git a/src/components/base/b-slider/props.ts b/src/components/base/b-slider/props.ts index 884ee7fb8f..be43f85e71 100644 --- a/src/components/base/b-slider/props.ts +++ b/src/components/base/b-slider/props.ts @@ -12,7 +12,7 @@ import iData, { component, prop } from 'components/super/i-data/i-data'; import { sliderModes, alignTypes } from 'components/base/b-slider/const'; import type { Mode, AlignType } from 'components/base/b-slider/interface'; -@component() +@component({partial: 'b-slider'}) export default abstract class iSliderProps extends iData { /** {@link iItems.Item} */ readonly Item!: object; diff --git a/src/components/base/b-tree/props.ts b/src/components/base/b-tree/props.ts index 7463240117..b7bda4375c 100644 --- a/src/components/base/b-tree/props.ts +++ b/src/components/base/b-tree/props.ts @@ -17,7 +17,7 @@ import type { Item, LazyRender, RenderFilter } from 'components/base/b-tree/inte import type AsyncRender from 'components/friends/async-render'; import type { TaskFilter } from 'components/friends/async-render'; -@component() +@component({partial: 'b-tree'}) export default abstract class iTreeProps extends iData { /** {@link iItems.Item} */ readonly Item!: Item; diff --git a/src/components/base/b-virtual-scroll-new/handlers.ts b/src/components/base/b-virtual-scroll-new/handlers.ts index 95d6f6ac24..95536093d6 100644 --- a/src/components/base/b-virtual-scroll-new/handlers.ts +++ b/src/components/base/b-virtual-scroll-new/handlers.ts @@ -24,7 +24,7 @@ const $$ = symbolGenerator(); * A class that provides an API to handle events emitted by the {@link bVirtualScrollNew} component. * This class is designed to work in conjunction with {@link bVirtualScrollNew}. */ -@component() +@component({partial: 'b-virtual-scroll-new'}) export abstract class iVirtualScrollHandlers extends iVirtualScrollProps { /** * Handler: component reset event. diff --git a/src/components/base/b-virtual-scroll-new/props.ts b/src/components/base/b-virtual-scroll-new/props.ts index 39beeb3d06..a94b970507 100644 --- a/src/components/base/b-virtual-scroll-new/props.ts +++ b/src/components/base/b-virtual-scroll-new/props.ts @@ -32,7 +32,7 @@ import type { Observer } from 'components/base/b-virtual-scroll-new/modules/obse import iData, { component, prop } from 'components/super/i-data/i-data'; -@component() +@component({partial: 'b-virtual-scroll-new'}) export default abstract class iVirtualScrollProps extends iData { /** {@link iItems.item} */ readonly Item!: object; diff --git a/src/components/form/b-button/props.ts b/src/components/form/b-button/props.ts index 1e5ca43f59..93f852c383 100644 --- a/src/components/form/b-button/props.ts +++ b/src/components/form/b-button/props.ts @@ -25,7 +25,7 @@ import iData, { import type bButton from 'components/form/b-button/b-button'; import type { ButtonType } from 'components/form/b-button/interface'; -@component() +@component({partial: 'i-button'}) export default abstract class iButtonProps extends iData { override readonly dataProviderProp: DataProviderProp = 'Provider'; override readonly defaultRequestFilter: RequestFilter = true; diff --git a/src/components/form/b-form/props.ts b/src/components/form/b-form/props.ts index 78378f77b3..37208afcdd 100644 --- a/src/components/form/b-form/props.ts +++ b/src/components/form/b-form/props.ts @@ -22,7 +22,7 @@ import iData, { import type { ActionFn } from 'components/form/b-form/interface'; -@component() +@component({partial: 'b-form'}) export default abstract class iFormProps extends iData { override readonly dataProviderProp: DataProviderProp = 'Provider'; override readonly defaultRequestFilter: RequestFilter = true; diff --git a/src/components/form/b-select/props.ts b/src/components/form/b-select/props.ts index 96305d7cf4..77d186f646 100644 --- a/src/components/form/b-select/props.ts +++ b/src/components/form/b-select/props.ts @@ -13,7 +13,7 @@ import type iActiveItems from 'components/traits/i-active-items/i-active-items'; import iInputText, { component, prop } from 'components/super/i-input-text/i-input-text'; import type { Value, FormValue, Item } from 'components/form/b-select/interface'; -@component() +@component({partial: 'b-select'}) export default abstract class iSelectProps extends iInputText { override readonly Value!: Value; diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 49f8110227..178e4c38bb 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -59,7 +59,7 @@ import iBlockFriends from 'components/super/i-block/friends'; const $$ = symbolGenerator(); -@component() +@component({partial: 'i-block'}) export default abstract class iBlockBase extends iBlockFriends { override readonly Component!: iBlock; override readonly Root!: iStaticPage; diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index 4d4943e542..daf9d4e632 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -36,7 +36,7 @@ export * from 'components/super/i-block/event/interface'; const $$ = symbolGenerator(); -@component() +@component({partial: 'i-block'}) export default abstract class iBlockEvent extends iBlockBase { /** * Associative type for typing events emitted by the component. diff --git a/src/components/super/i-block/friends.ts b/src/components/super/i-block/friends.ts index 0cffe4e436..e2372c0f78 100644 --- a/src/components/super/i-block/friends.ts +++ b/src/components/super/i-block/friends.ts @@ -35,7 +35,7 @@ import { system, hook } from 'components/super/i-block/decorators'; import iBlockProps from 'components/super/i-block/props'; -@component() +@component({partial: 'i-block'}) export default abstract class iBlockFriends extends iBlockProps { /** * A class with methods to provide component classes/styles to another component, etc diff --git a/src/components/super/i-block/mods/index.ts b/src/components/super/i-block/mods/index.ts index b901b8b7f2..4aa43c5c41 100644 --- a/src/components/super/i-block/mods/index.ts +++ b/src/components/super/i-block/mods/index.ts @@ -21,7 +21,7 @@ import iBlockEvent from 'components/super/i-block/event'; export * from 'components/super/i-block/mods/interface'; -@component() +@component({partial: 'i-block'}) export default abstract class iBlockMods extends iBlockEvent { @system({merge: mergeMods, init: initMods}) override readonly mods!: ModsDict; diff --git a/src/components/super/i-block/props.ts b/src/components/super/i-block/props.ts index 00ffb3ed0b..212f5cfff2 100644 --- a/src/components/super/i-block/props.ts +++ b/src/components/super/i-block/props.ts @@ -16,7 +16,7 @@ import type { ModsProp } from 'components/super/i-block/modules/mods'; import { prop, DecoratorMethodWatcher } from 'components/super/i-block/decorators'; import type { TransitionMethod } from 'components/base/b-router/interface'; -@component() +@component({partial: 'i-block'}) export default abstract class iBlockProps extends ComponentInterface { @prop({type: String, required: false}) override readonly componentIdProp?: string; diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index fe1886f067..633eccbe9d 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -39,7 +39,7 @@ export * from 'components/super/i-block/providers/interface'; const $$ = symbolGenerator(); -@component() +@component({partial: 'i-block'}) export default abstract class iBlockProviders extends iBlockState { /** {@link iBlock.dontWaitRemoteProvidersProp} */ @system((o) => o.sync.link((val) => { diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index f730f54482..d3c60cd3fd 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -33,7 +33,7 @@ import type { Stage, ComponentStatus, ComponentStatuses } from 'components/super import iBlockMods from 'components/super/i-block/mods'; import type { Theme } from 'core/theme-manager'; -@component() +@component({partial: 'i-block'}) export default abstract class iBlockState extends iBlockMods { /** * A list of additional dependencies to load during the component's initialization diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index bcdd76adee..08ee035d90 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -43,7 +43,11 @@ const interface iDataData extends Trait {} -@component({functional: null}) +@component({ + partial: 'i-data', + functional: null +}) + @derive(iDataProvider) abstract class iDataData extends iBlock implements iDataProvider { /** diff --git a/src/components/super/i-data/handlers.ts b/src/components/super/i-data/handlers.ts index 82df55f16a..a6fd837f38 100644 --- a/src/components/super/i-data/handlers.ts +++ b/src/components/super/i-data/handlers.ts @@ -20,7 +20,11 @@ import type { RequestParams, RetryRequestFn } from 'components/super/i-data/inte const $$ = symbolGenerator(); -@component({functional: null}) +@component({ + partial: 'i-data', + functional: null +}) + export default abstract class iDataHandlers extends iDataData { protected override initGlobalEvents(resetListener?: boolean): void { super.initGlobalEvents(resetListener != null ? resetListener : Boolean(this.dataProvider)); diff --git a/src/components/super/i-input/fields.ts b/src/components/super/i-input/fields.ts index c9bdf6fdaf..d1d0bbb902 100644 --- a/src/components/super/i-input/fields.ts +++ b/src/components/super/i-input/fields.ts @@ -26,7 +26,7 @@ import type { } from 'components/super/i-input/interface'; -@component() +@component({partial: 'i-input'}) export default abstract class iInputFields extends iInputProps { /** * A list of component value(s) that cannot be submitted via the associated form diff --git a/src/components/super/i-input/modules/handlers.ts b/src/components/super/i-input/modules/handlers.ts index abaece5832..06e9b60234 100644 --- a/src/components/super/i-input/modules/handlers.ts +++ b/src/components/super/i-input/modules/handlers.ts @@ -13,7 +13,7 @@ import { component, ModEvent } from 'components/super/i-data/i-data'; import iInputFields from 'components/super/i-input/fields'; -@component() +@component({partial: 'i-input'}) export default abstract class iInputHandlers extends iInputFields { protected override initRemoteData(): CanUndef> { if (this.db == null) { diff --git a/src/components/super/i-input/props.ts b/src/components/super/i-input/props.ts index cac9708b0c..e89fbfe5d5 100644 --- a/src/components/super/i-input/props.ts +++ b/src/components/super/i-input/props.ts @@ -14,7 +14,7 @@ import type { Value, FormValue, Validator } from 'components/super/i-input/inter import { unpackIf } from 'components/super/i-input/modules/helpers'; -@component() +@component({partial: 'i-input'}) export default abstract class iInputProps extends iData { /** * Type: the value of the component diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index 61c0c07199..e8b3e5643e 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -128,8 +128,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { }); ['Store', 'Prop'].forEach((postfix) => { - const - path = name + postfix; + const path = name + postfix; if (path in this) { // @ts-ignore (effect) diff --git a/src/core/component/const/cache.ts b/src/core/component/const/cache.ts index 44df73af25..be8c558cb9 100644 --- a/src/core/component/const/cache.ts +++ b/src/core/component/const/cache.ts @@ -17,6 +17,7 @@ import type { ComponentMeta, ComponentOptions, + ComponentConstructorInfo, RenderFactory, App @@ -28,6 +29,11 @@ import type { */ export const componentParams = new Map(); +/** + * A dictionary with the component declaration parameters + */ +export const partialInfo = new Map(); + /** * A dictionary with the registered root components */ diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index c58d47bb52..70877f25b3 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -25,11 +25,11 @@ import { } from 'core/component/const'; import { initEmitter } from 'core/component/event'; -import { createMeta, fillMeta, attachTemplatesToMeta } from 'core/component/meta'; +import { createMeta, fillMeta, attachTemplatesToMeta, addMethodsToMeta } from 'core/component/meta'; import { getInfoFromConstructor } from 'core/component/reflect'; import { getComponent, ComponentEngine } from 'core/component/engines'; -import { registerParentComponents } from 'core/component/init'; +import { registerComponent, registerParentComponents } from 'core/component/init'; import type { ComponentConstructor, ComponentOptions } from 'core/component/interface'; @@ -65,30 +65,48 @@ export function component(opts?: ComponentOptions): Function { return (target: ComponentConstructor) => { const componentInfo = getInfoFromConstructor(target, opts), - componentParams = componentInfo.params; + componentParams = componentInfo.params, + partOf = componentParams.partial != null; - if (componentInfo.name === componentInfo.parentParams?.name) { + const + componentName = componentInfo.name, + hasSameName = !partOf && componentName === componentInfo.parentParams?.name; + + if (hasSameName) { Object.defineProperty(componentInfo.parent!, OVERRIDDEN, {value: true}); } - initEmitter - .emit('bindConstructor', componentInfo.name); + initEmitter.emit('bindConstructor', componentName); + + if (partOf) { + let meta = components.get(componentName); + + if (meta == null) { + meta = createMeta(componentInfo); + components.set(componentName, meta); + } + + addMethodsToMeta(meta, target); + return; + } - if (!Object.isTruly(componentInfo.name) || componentParams.root || componentInfo.isAbstract) { + if (!Object.isTruly(componentName) || componentParams.root === true || componentInfo.isAbstract) { regComponent(); } else { - const initList = componentRegInitializers[componentInfo.name] ?? []; - componentRegInitializers[componentInfo.name] = initList; + const initList = componentRegInitializers[componentName] ?? []; + componentRegInitializers[componentName] = initList; initList.push(regComponent); + + requestIdleCallback(() => registerComponent(componentName)); } // If we have a smart component, - // we need to compile two components in the runtime + // we need to compile two components at runtime if (Object.isPlainObject(componentParams.functional)) { component({ ...opts, - name: `${componentInfo.name}-functional`, + name: `${componentName}-functional`, functional: true })(target); } @@ -96,23 +114,30 @@ export function component(opts?: ComponentOptions): Function { function regComponent(): void { registerParentComponents(componentInfo); - const - {parentMeta} = componentInfo; + let rawMeta = components.get(componentName); + + if (rawMeta == null) { + rawMeta = createMeta(componentInfo); + components.set(componentName, rawMeta); - const - meta = createMeta(componentInfo), - componentName = componentInfo.name; + } else { + rawMeta.constructor = target; + } - if (componentInfo.params.name == null || !componentInfo.isSmart) { + const meta = rawMeta; + + if (componentParams.name == null || !componentInfo.isSmart) { components.set(target, meta); } - components.set(componentName, meta); - initEmitter.emit(`constructor.${componentName}`, {meta, parentMeta}); + initEmitter.emit(`constructor.${componentName}`, { + meta, + parentMeta: componentInfo.parentMeta + }); const noNeedToRegisterAsComponent = - target.hasOwnProperty(OVERRIDDEN) || componentInfo.isAbstract || + target.hasOwnProperty(OVERRIDDEN) || !SSR && meta.params.functional === true; if (noNeedToRegisterAsComponent) { @@ -139,8 +164,7 @@ export function component(opts?: ComponentOptions): Function { return meta.params.tpl === false ? attachTemplatesAndResolve() : waitComponentTemplates(); function waitComponentTemplates() { - const - fns = TPLS[meta.componentName]; + const fns = TPLS[meta.componentName]; if (fns != null) { return attachTemplatesAndResolve(fns); diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index 37cfc96134..ca9353ddef 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -28,11 +28,9 @@ import * as r from 'core/component/engines/vue3/render'; * @param meta */ export function getComponent(meta: ComponentMeta): ComponentOptions { - const - {component} = fillMeta(meta); + const {component} = fillMeta(meta); - const - p = meta.params; + const p = meta.params; return { ...Object.cast(component), diff --git a/src/core/component/init/component.ts b/src/core/component/init/component.ts index c4f7a451cf..342984d05e 100644 --- a/src/core/component/init/component.ts +++ b/src/core/component/init/component.ts @@ -21,8 +21,7 @@ import type { ComponentConstructorInfo } from 'core/component/reflect'; * @param component - the component information object */ export function registerParentComponents(component: ComponentConstructorInfo): boolean { - const - {name} = component; + const {name} = component; let parentName = component.parentParams?.name, @@ -44,8 +43,7 @@ export function registerParentComponents(component: ComponentConstructorInfo): b if (Object.isTruly(parentName)) { parentName = parentName; - const - regParentComponent = componentRegInitializers[parentName]; + const regParentComponent = componentRegInitializers[parentName]; if (regParentComponent != null) { regParentComponent.forEach((reg) => reg()); @@ -72,8 +70,7 @@ export function registerComponent(name: CanUndef): CanNull reg()); diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index b900798319..610918aa0b 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -122,7 +122,7 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { } }); - if (component.parentMeta) { + if (component.parentMeta != null) { inheritMeta(meta, component.parentMeta); } diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index ecbc39211b..ef5a8f363f 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -22,7 +22,10 @@ import type { ComponentConstructor, ComponentMeta, ModVal } from 'core/component * @param meta * @param [constructor] - the component constructor */ -export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor = meta.constructor): ComponentMeta { +export function fillMeta( + meta: ComponentMeta, + constructor: ComponentConstructor = meta.constructor +): ComponentMeta { addMethodsToMeta(meta, constructor); if (isAbstractComponent.test(meta.componentName)) { @@ -195,6 +198,10 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor component.methods[name] = wrapper; + if (method.fn == null) { + console.log(constructor.name, name); + } + if (wrapper.length !== method.fn.length) { Object.defineProperty(wrapper, 'length', {get: () => method.fn.length}); } diff --git a/src/core/component/meta/interface/index.ts b/src/core/component/meta/interface/index.ts index 3076cbbde9..b0f106d068 100644 --- a/src/core/component/meta/interface/index.ts +++ b/src/core/component/meta/interface/index.ts @@ -67,7 +67,7 @@ export interface ComponentMeta { /** * A link to the metaobject of the parent component */ - parentMeta?: ComponentMeta; + parentMeta: CanNull; /** * A dictionary that contains the input properties (props) for the component diff --git a/src/core/component/meta/interface/options.ts b/src/core/component/meta/interface/options.ts index f96ec88f3b..7967abe169 100644 --- a/src/core/component/meta/interface/options.ts +++ b/src/core/component/meta/interface/options.ts @@ -32,6 +32,8 @@ export interface ComponentOptions { */ name?: string; + partial?: string; + /** * If set to true, the component is registered as the root component. * The root component sits at the top of the component hierarchy and contains all components in the application. diff --git a/src/core/component/reflect/const.ts b/src/core/component/reflect/const.ts index ea2d94025d..8acf99528c 100644 --- a/src/core/component/reflect/const.ts +++ b/src/core/component/reflect/const.ts @@ -12,7 +12,11 @@ export const isPropGetter = { }, replace(name: string): string { - return name.startsWith('@') ? name.slice('@:'.length) : name.slice('on:'.length); + if (isPropGetter.test(name)) { + return name.startsWith('@') ? name.slice('@:'.length) : name.slice('on:'.length); + } + + return name; } }; @@ -42,7 +46,7 @@ export const isBinding = { }, replace(name: string): string { - return name.startsWith('p') ? name.slice('Prop'.length) : name.slice('Store'.length); + return name.endsWith('p') ? name.slice(0, name.length - 'Prop'.length) : isStore.replace(name); } }; diff --git a/src/core/component/reflect/constructor.ts b/src/core/component/reflect/constructor.ts index 8eb9339ddd..a3dfafaa7c 100644 --- a/src/core/component/reflect/constructor.ts +++ b/src/core/component/reflect/constructor.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { componentParams, components } from 'core/component/const'; +import { components, componentParams, partialInfo } from 'core/component/const'; import { isAbstractComponent, isSmartComponent } from 'core/component/reflect/validators'; import type { ComponentOptions, ComponentMeta, ComponentConstructor } from 'core/component/interface'; @@ -63,29 +63,69 @@ export function getInfoFromConstructor( constructor: ComponentConstructor, declParams?: ComponentOptions ): ComponentConstructorInfo { + const partial = declParams?.partial?.dasherize(); + + if (partial != null) { + let info = partialInfo.get(partial); + + if (info == null) { + const {parent, parentParams} = getParent(); + + info = { + name: partial, + componentName: partial, + + constructor, + params: {...declParams, partial}, + + isAbstract: true, + isSmart: false, + + parent, + parentParams, + + get parentMeta() { + return components.get(parent) ?? null; + } + }; + + partialInfo.set(partial, info); + } + + componentParams.set(constructor, info.params); + return info; + } + const name = declParams?.name ?? getComponentName(constructor), layer = declParams?.layer; - const - parent = Object.getPrototypeOf(constructor), - parentParams = parent != null ? componentParams.get(parent) : undefined; + let {parent, parentParams} = getParent(); + + if (parentParams?.partial != null) { + ({parent, parentParams} = partialInfo.get(parentParams.partial)!); + } // Create an object with the component parameters const params = parentParams != null ? { root: parentParams.root, ...declParams, - name + + name, + partial: undefined } : { - root: false, tpl: true, + root: false, + inheritAttrs: true, functional: false, ...declParams, - name + + name, + partial: undefined }; if (SSR) { @@ -128,8 +168,16 @@ export function getInfoFromConstructor( parent, parentParams, - get parentMeta(): CanUndef { - return parent != null ? components.get(parent) : undefined; + get parentMeta(): CanNull { + return components.get(parent) ?? null; } }; + + function getParent() { + const + parent = Object.getPrototypeOf(constructor), + parentParams = componentParams.get(parent) ?? null; + + return {parent, parentParams}; + } } diff --git a/src/core/component/reflect/interface.ts b/src/core/component/reflect/interface.ts index a5a4ea0d2e..95fafa203e 100644 --- a/src/core/component/reflect/interface.ts +++ b/src/core/component/reflect/interface.ts @@ -55,17 +55,17 @@ export interface ComponentConstructorInfo { /** * A link to the parent component's constructor */ - parent?: Function; + parent: CanNull; /** * A dictionary containing the parent component's parameters that were passed to the @component decorator */ - parentParams?: ComponentOptions; + parentParams: CanNull; /** * A link to the metaobject of the parent component */ - parentMeta?: ComponentMeta; + parentMeta: CanNull; /** * The name of the NPM package in which the component is defined or overridden diff --git a/src/core/component/render/helpers/normalizers.ts b/src/core/component/render/helpers/normalizers.ts index 59adb3e505..fa4efdf79c 100644 --- a/src/core/component/render/helpers/normalizers.ts +++ b/src/core/component/render/helpers/normalizers.ts @@ -168,7 +168,7 @@ export function normalizeComponentAttrs( dynamicProps.push(tiedPropName); } - if (propName in props || isPropGetter.test(attrName) && isPropGetter.replace(attrName) in props) { + if (propName in props || isPropGetter.replace(propName) in props) { changeAttrName(attrName, propName); } else { From 232911b0cb699696f78d388cfa42c21c57b33717 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 9 Aug 2024 14:55:40 +0300 Subject: [PATCH 004/334] chore: improved dsl --- src/core/component/const/validators.ts | 13 +- .../component/decorators/component/index.ts | 58 ++++---- src/core/component/event/emitter.ts | 39 +---- src/core/component/meta/fill.ts | 140 ++++++++++-------- src/core/component/meta/method.ts | 10 ++ src/core/component/render/wrappers.ts | 2 +- 6 files changed, 134 insertions(+), 128 deletions(-) diff --git a/src/core/component/const/validators.ts b/src/core/component/const/validators.ts index 2ab758492c..7fe481310d 100644 --- a/src/core/component/const/validators.ts +++ b/src/core/component/const/validators.ts @@ -6,6 +6,13 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +const componentPrefixes = new Set([ + 'b-', + 'g-', + 'p-', + 'i-' +]); + /** * A RegExp to check if the given string is the name of a component * @@ -15,4 +22,8 @@ * console.log(isComponent.test('button')); // false * ``` */ -export const isComponent = /^([bgp]-[^ _]+)$/; +export const isComponent = { + test(name: string): boolean { + return componentPrefixes.has(name.slice(0, 2)) && !name.includes(' ', 2); + } +}; diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 70877f25b3..f786b8c935 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -69,36 +69,38 @@ export function component(opts?: ComponentOptions): Function { partOf = componentParams.partial != null; const - componentName = componentInfo.name, - hasSameName = !partOf && componentName === componentInfo.parentParams?.name; + componentOriginName = componentInfo.name, + componentNormalizedName = componentInfo.componentName, + hasSameOrigin = !partOf && componentOriginName === componentInfo.parentParams?.name; - if (hasSameName) { + if (hasSameOrigin) { Object.defineProperty(componentInfo.parent!, OVERRIDDEN, {value: true}); } - initEmitter.emit('bindConstructor', componentName); + initEmitter.emit('bindConstructor', componentOriginName); if (partOf) { - let meta = components.get(componentName); + pushToInitList(() => { + let meta = components.get(componentOriginName); - if (meta == null) { - meta = createMeta(componentInfo); - components.set(componentName, meta); - } + if (meta == null) { + meta = createMeta(componentInfo); + components.set(componentOriginName, meta); + } + + addMethodsToMeta(meta, target); + }); - addMethodsToMeta(meta, target); return; } - if (!Object.isTruly(componentName) || componentParams.root === true || componentInfo.isAbstract) { - regComponent(); + pushToInitList(regComponent); - } else { - const initList = componentRegInitializers[componentName] ?? []; - componentRegInitializers[componentName] = initList; - initList.push(regComponent); + if (!Object.isTruly(componentOriginName) || componentParams.root === true || componentInfo.isAbstract) { + registerComponent(componentOriginName); - requestIdleCallback(() => registerComponent(componentName)); + } else { + requestIdleCallback(() => registerComponent(componentOriginName)); } // If we have a smart component, @@ -106,22 +108,28 @@ export function component(opts?: ComponentOptions): Function { if (Object.isPlainObject(componentParams.functional)) { component({ ...opts, - name: `${componentName}-functional`, + name: `${componentOriginName}-functional`, functional: true })(target); } + function pushToInitList(init: Function) { + const initList = componentRegInitializers[componentOriginName] ?? []; + componentRegInitializers[componentOriginName] = initList; + initList.push(init); + } + function regComponent(): void { registerParentComponents(componentInfo); - let rawMeta = components.get(componentName); + let rawMeta = components.get(componentNormalizedName); if (rawMeta == null) { rawMeta = createMeta(componentInfo); - components.set(componentName, rawMeta); + components.set(componentNormalizedName, rawMeta); } else { - rawMeta.constructor = target; + rawMeta = {...rawMeta, watchers: {}, constructor: target}; } const meta = rawMeta; @@ -130,7 +138,7 @@ export function component(opts?: ComponentOptions): Function { components.set(target, meta); } - initEmitter.emit(`constructor.${componentName}`, { + initEmitter.emit(`constructor.${componentInfo.componentName}`, { meta, parentMeta: componentInfo.parentMeta }); @@ -148,13 +156,13 @@ export function component(opts?: ComponentOptions): Function { } } else if (meta.params.root) { - rootComponents[componentName] = loadTemplate(getComponent(meta)); + rootComponents[componentOriginName] = loadTemplate(getComponent(meta)); } else { - const componentDeclArgs = [componentName, loadTemplate(getComponent(meta))]; + const componentDeclArgs = [componentOriginName, loadTemplate(getComponent(meta))]; ComponentEngine.component(...componentDeclArgs); - if (app.context != null && app.context.component(componentName) == null) { + if (app.context != null && app.context.component(componentOriginName) == null) { app.context.component(...componentDeclArgs); } } diff --git a/src/core/component/event/emitter.ts b/src/core/component/event/emitter.ts index 223de3e507..b27d273d6a 100644 --- a/src/core/component/event/emitter.ts +++ b/src/core/component/event/emitter.ts @@ -6,17 +6,9 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { - - EventEmitter2 as EventEmitter, - - OnOptions, - ListenerFn - -} from 'eventemitter2'; +import { EventEmitter2 as EventEmitter } from 'eventemitter2'; import log from 'core/log'; -import { componentParams } from 'core/component/const'; /** * This event emitter is used to broadcast various external events from modules to a unified event bus for components @@ -43,32 +35,3 @@ export const initEmitter = new EventEmitter({ maxListeners: 1e3, newListener: false }); - -// We need to wrap the original `once` function of the emitter -// to attach logic of registering smart components -((initEventOnce) => { - initEmitter.once = function once( - event: CanArray, - listener: ListenerFn, - opts?: true | OnOptions - ): EventEmitter { - (Object.isArray(event) ? event : [event]).forEach((event) => { - const chunks = event.split('.', 2); - - if (chunks[0] === 'constructor') { - initEventOnce(event, listener, opts); - - const p = componentParams.get(chunks[1]); - - if (p != null && Object.isDictionary(p.functional)) { - initEventOnce(`${event}-functional`, listener, opts); - } - - } else { - initEventOnce(event, listener, opts); - } - }); - - return this; - }; -})(initEmitter.once.bind(initEmitter)); diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index ef5a8f363f..8469e7fa15 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -16,6 +16,10 @@ import { addMethodsToMeta } from 'core/component/meta/method'; import type { ComponentConstructor, ComponentMeta, ModVal } from 'core/component/interface'; +const + ALREADY_PASSED = Symbol('This target is passed'), + INSTANCE = Symbol('The component instance'); + /** * Populates the passed metaobject with methods and properties from the specified component class constructor * @@ -32,6 +36,9 @@ export function fillMeta( return meta; } + // For smart components, this method can be called more than once + const isFirstFill = !constructor.hasOwnProperty(ALREADY_PASSED); + const { component, params, @@ -47,15 +54,16 @@ export function fillMeta( watchPropDependencies } = meta; - let instance: unknown = null; - Object.defineProperty(meta, 'instance', { enumerable: true, configurable: true, get() { - instance ??= new constructor(); - return instance; + if (!constructor.hasOwnProperty(INSTANCE)) { + Object.defineProperty(constructor, INSTANCE, {value: new constructor()}); + } + + return constructor[INSTANCE]; } }); @@ -74,45 +82,47 @@ export function fillMeta( return; } - let - getDefault: unknown, - skipDefault = true; + if (isFirstFill) { + let + getDefault: unknown, + skipDefault = true; - if (defaultProps || prop.forceDefault) { - const defaultInstanceValue = meta.instance[name]; + if (defaultProps || prop.forceDefault) { + const defaultInstanceValue = meta.instance[name]; - skipDefault = false; - getDefault = defaultInstanceValue; + skipDefault = false; + getDefault = defaultInstanceValue; - // If the default value of a field is set through default values for a class property, - // then we need to clone it for each new component instance to ensure that they do not use a shared value - const needCloneDefValue = - defaultInstanceValue != null && typeof defaultInstanceValue === 'object' && - (!isTypeCanBeFunc(prop.type) || !Object.isFunction(defaultInstanceValue)); + // If the default value of a field is set through default values for a class property, + // then we need to clone it for each new component instance to ensure that they do not use a shared value + const needCloneDefValue = + defaultInstanceValue != null && typeof defaultInstanceValue === 'object' && + (!isTypeCanBeFunc(prop.type) || !Object.isFunction(defaultInstanceValue)); - if (needCloneDefValue) { - getDefault = () => Object.fastClone(defaultInstanceValue); - (getDefault)[DEFAULT_WRAPPER] = true; + if (needCloneDefValue) { + getDefault = () => Object.fastClone(defaultInstanceValue); + (getDefault)[DEFAULT_WRAPPER] = true; + } } - } - let defaultValue: unknown; + let defaultValue: unknown; - if (!skipDefault) { - defaultValue = prop.default !== undefined ? prop.default : getDefault; - } + if (!skipDefault) { + defaultValue = prop.default !== undefined ? prop.default : getDefault; + } - if (!isRoot || defaultValue !== undefined) { - (prop.forceUpdate ? component.props : component.attrs)[name] = { - type: prop.type, - required: prop.required !== false && defaultProps && defaultValue === undefined, + if (!isRoot || defaultValue !== undefined) { + (prop.forceUpdate ? component.props : component.attrs)[name] = { + type: prop.type, + required: prop.required !== false && defaultProps && defaultValue === undefined, - default: defaultValue, - functional: prop.functional, + default: defaultValue, + functional: prop.functional, - // eslint-disable-next-line @v4fire/unbound-method - validator: prop.validator - }; + // eslint-disable-next-line @v4fire/unbound-method + validator: prop.validator + }; + } } if (Object.size(prop.watchers) > 0) { @@ -178,16 +188,18 @@ export function fillMeta( // Computed fields - Object.entries(computedFields).forEach(([name, computed]) => { - if (computed == null || computed.cache !== 'auto') { - return; - } + if (isFirstFill) { + Object.entries(computedFields).forEach(([name, computed]) => { + if (computed == null || computed.cache !== 'auto') { + return; + } - component.computed[name] = { - get: computed.get, - set: computed.set - }; - }); + component.computed[name] = { + get: computed.get, + set: computed.set + }; + }); + } // Methods @@ -196,14 +208,12 @@ export function fillMeta( return; } - component.methods[name] = wrapper; - - if (method.fn == null) { - console.log(constructor.name, name); - } + if (isFirstFill) { + component.methods[name] = wrapper; - if (wrapper.length !== method.fn.length) { - Object.defineProperty(wrapper, 'length', {get: () => method.fn.length}); + if (wrapper.length !== method.fn.length) { + Object.defineProperty(wrapper, 'length', {get: () => method.fn.length}); + } } if (method.watchers != null) { @@ -245,24 +255,28 @@ export function fillMeta( // Modifiers - const {mods} = component; + if (isFirstFill) { + const {mods} = component; - Object.entries(meta.mods).forEach(([key, mod]) => { - let defaultValue: CanUndef; + Object.entries(meta.mods).forEach(([key, mod]) => { + let defaultValue: CanUndef; - if (mod != null) { - mod.some((val) => { - if (Object.isArray(val)) { - defaultValue = val; - return true; - } + if (mod != null) { + mod.some((val) => { + if (Object.isArray(val)) { + defaultValue = val; + return true; + } - return false; - }); + return false; + }); - mods[key] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; - } - }); + mods[key] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; + } + }); + } + + Object.defineProperty(constructor, ALREADY_PASSED, {value: true}); return meta; } diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index 881167da5e..a21dd7abe4 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -9,6 +9,9 @@ import { defProp } from 'core/const/props'; import type { ComponentMeta } from 'core/component/interface'; +const + ALREADY_PASSED = Symbol('This target is passed'); + /** * Loops through the prototype of the passed component constructor and * adds methods and accessors to the specified metaobject @@ -17,6 +20,13 @@ import type { ComponentMeta } from 'core/component/interface'; * @param [constructor] */ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = meta.constructor): void { + // For smart components, this method can be called more than once + if (constructor.hasOwnProperty(ALREADY_PASSED)) { + return; + } + + Object.defineProperty(constructor, ALREADY_PASSED, {value: true}); + const { componentName: src, props, diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 2f373912ab..90d50de810 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -262,7 +262,7 @@ export function wrapResolveComponent Date: Fri, 9 Aug 2024 16:15:15 +0300 Subject: [PATCH 005/334] fix: fixed smart components creation --- src/core/component/decorators/component/index.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index f786b8c935..3cc5e5761b 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -130,10 +130,19 @@ export function component(opts?: ComponentOptions): Function { } else { rawMeta = {...rawMeta, watchers: {}, constructor: target}; + + rawMeta.params = { + ...componentInfo.parentParams, + ...componentInfo.params, + // eslint-disable-next-line deprecation/deprecation + deprecatedProps: {...componentInfo.parentParams?.deprecatedProps, ...componentInfo.params.deprecatedProps} + }; } const meta = rawMeta; + components.set(componentOriginName, meta); + if (componentParams.name == null || !componentInfo.isSmart) { components.set(target, meta); } From 5bd50e639bff708437515852ac991eb1d0cf0c7a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 9 Aug 2024 18:15:52 +0300 Subject: [PATCH 006/334] fix: fixed smart components creation --- src/components/form/b-button/props.ts | 2 +- src/components/form/b-button/test/unit/main.ts | 4 ++-- .../component/decorators/component/index.ts | 17 ++++++++++++++--- src/core/component/meta/create.ts | 2 +- src/core/component/meta/fill.ts | 4 ++-- src/core/component/meta/inherit.ts | 17 +++++++++++------ 6 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/components/form/b-button/props.ts b/src/components/form/b-button/props.ts index 93f852c383..a745d0081a 100644 --- a/src/components/form/b-button/props.ts +++ b/src/components/form/b-button/props.ts @@ -25,7 +25,7 @@ import iData, { import type bButton from 'components/form/b-button/b-button'; import type { ButtonType } from 'components/form/b-button/interface'; -@component({partial: 'i-button'}) +@component({partial: 'b-button'}) export default abstract class iButtonProps extends iData { override readonly dataProviderProp: DataProviderProp = 'Provider'; override readonly defaultRequestFilter: RequestFilter = true; diff --git a/src/components/form/b-button/test/unit/main.ts b/src/components/form/b-button/test/unit/main.ts index df02b09fa8..2c2e48244a 100644 --- a/src/components/form/b-button/test/unit/main.ts +++ b/src/components/form/b-button/test/unit/main.ts @@ -95,7 +95,7 @@ test.describe('', () => { iconName = 'foo'; await renderButton(page, { - preIcon: iconName + icon: iconName }); const @@ -107,7 +107,7 @@ test.describe('', () => { test('`undefined`', async ({page}) => { await renderButton(page, { - preIcon: undefined + icon: undefined }); const icon = await $el.$('svg'); diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 3cc5e5761b..0909b48072 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -25,8 +25,8 @@ import { } from 'core/component/const'; import { initEmitter } from 'core/component/event'; -import { createMeta, fillMeta, attachTemplatesToMeta, addMethodsToMeta } from 'core/component/meta'; -import { getInfoFromConstructor } from 'core/component/reflect'; +import { createMeta, fillMeta, inheritMods, attachTemplatesToMeta, addMethodsToMeta } from 'core/component/meta'; +import { getComponentMods, getInfoFromConstructor } from 'core/component/reflect'; import { getComponent, ComponentEngine } from 'core/component/engines'; import { registerComponent, registerParentComponents } from 'core/component/init'; @@ -129,7 +129,14 @@ export function component(opts?: ComponentOptions): Function { components.set(componentNormalizedName, rawMeta); } else { - rawMeta = {...rawMeta, watchers: {}, constructor: target}; + const newTarget = target !== rawMeta.constructor; + + rawMeta = { + ...rawMeta, + watchers: {}, + constructor: target, + mods: newTarget ? getComponentMods(componentInfo) : rawMeta.mods + }; rawMeta.params = { ...componentInfo.parentParams, @@ -137,6 +144,10 @@ export function component(opts?: ComponentOptions): Function { // eslint-disable-next-line deprecation/deprecation deprecatedProps: {...componentInfo.parentParams?.deprecatedProps, ...componentInfo.params.deprecatedProps} }; + + if (newTarget && componentInfo.parentMeta != null) { + inheritMods(rawMeta, componentInfo.parentMeta); + } } const meta = rawMeta; diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index 610918aa0b..a96ad4e4fd 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -29,7 +29,7 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { params: component.params, props: {}, - mods: getComponentMods(component), + mods: component.params.partial == null ? getComponentMods(component) : {}, fields: {}, tiedFields: {}, diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 8469e7fa15..8c839800b1 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -258,7 +258,7 @@ export function fillMeta( if (isFirstFill) { const {mods} = component; - Object.entries(meta.mods).forEach(([key, mod]) => { + Object.entries(meta.mods).forEach(([name, mod]) => { let defaultValue: CanUndef; if (mod != null) { @@ -271,7 +271,7 @@ export function fillMeta( return false; }); - mods[key] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; + mods[name] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; } }); } diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 27507edca6..3dff5b0661 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -25,9 +25,7 @@ export function inheritMeta( const { params: pParams, - props: pProps, - mods: pMods, fields: pFields, tiedFields: pTiedFields, @@ -155,8 +153,17 @@ export function inheritMeta( // Modifiers inheritance - const - {mods} = meta; + if (meta.params.partial != null) { + inheritMods(meta, parentMeta); + } + + return meta; +} + +export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): void { + const {mods: pMods} = parentMeta; + + const {mods} = meta; Object.keys(pMods).forEach((name) => { const @@ -220,6 +227,4 @@ export function inheritMeta( mods[name] = parentMods; } }); - - return meta; } From 1a9f856ad27a4c3bcb32d137d58efef359611903 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 12 Aug 2024 17:41:39 +0300 Subject: [PATCH 007/334] fix: fixed smart components creation --- .../component/decorators/component/index.ts | 48 ++++++++++++------- src/core/component/meta/fill.ts | 22 +++++++++ 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 0909b48072..dc3cb60a91 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -88,7 +88,9 @@ export function component(opts?: ComponentOptions): Function { components.set(componentOriginName, meta); } - addMethodsToMeta(meta, target); + initEmitter.once(`constructor.${componentInfo.componentName}`, () => { + addMethodsToMeta(meta!, target); + }); }); return; @@ -131,26 +133,40 @@ export function component(opts?: ComponentOptions): Function { } else { const newTarget = target !== rawMeta.constructor; - rawMeta = { - ...rawMeta, - watchers: {}, - constructor: target, - mods: newTarget ? getComponentMods(componentInfo) : rawMeta.mods - }; - - rawMeta.params = { - ...componentInfo.parentParams, - ...componentInfo.params, - // eslint-disable-next-line deprecation/deprecation - deprecatedProps: {...componentInfo.parentParams?.deprecatedProps, ...componentInfo.params.deprecatedProps} - }; + rawMeta = Object.create(rawMeta, { + constructor: { + enumerable: true, + writable: true, + configurable: true, + value: target + }, + + mods: { + enumerable: true, + writable: true, + configurable: true, + value: newTarget ? getComponentMods(componentInfo) : rawMeta.mods + }, + + params: { + enumerable: true, + writable: true, + configurable: true, + value: { + ...componentInfo.parentParams, + ...componentInfo.params, + // eslint-disable-next-line deprecation/deprecation + deprecatedProps: {...componentInfo.parentParams?.deprecatedProps, ...componentInfo.params.deprecatedProps} + } + } + }); if (newTarget && componentInfo.parentMeta != null) { - inheritMods(rawMeta, componentInfo.parentMeta); + inheritMods(rawMeta!, componentInfo.parentMeta); } } - const meta = rawMeta; + const meta = rawMeta!; components.set(componentOriginName, meta); diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 8c839800b1..bff00428a3 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -18,6 +18,7 @@ import type { ComponentConstructor, ComponentMeta, ModVal } from 'core/component const ALREADY_PASSED = Symbol('This target is passed'), + BLUEPRINT = Symbol('This is a meta blueprint'), INSTANCE = Symbol('The component instance'); /** @@ -39,6 +40,27 @@ export function fillMeta( // For smart components, this method can be called more than once const isFirstFill = !constructor.hasOwnProperty(ALREADY_PASSED); + if (meta[BLUEPRINT] == null) { + Object.defineProperty(meta, BLUEPRINT, { + value: { + watchers: meta.watchers, + hooks: meta.hooks + } + }); + } + + const blueprint: Pick< + ComponentMeta, + 'watchers' | 'hooks' + > = meta[BLUEPRINT]; + + Object.assign(meta, { + watchers: {...blueprint.watchers}, + hooks: Object.fromEntries( + Object.entries(blueprint.hooks).map(([key, val]) => [key, val.slice()]) + ) + }); + const { component, params, From e5d9587b5cbe19d3726855509ea31f23f08c7540 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 13 Aug 2024 14:21:26 +0300 Subject: [PATCH 008/334] fix: fixed smart components creation --- src/core/component/const/validators.ts | 6 +++- src/core/component/meta/fill.ts | 44 +++++++++++++------------- src/core/component/meta/inherit.ts | 2 +- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/core/component/const/validators.ts b/src/core/component/const/validators.ts index 7fe481310d..5ac34accfd 100644 --- a/src/core/component/const/validators.ts +++ b/src/core/component/const/validators.ts @@ -23,7 +23,11 @@ const componentPrefixes = new Set([ * ``` */ export const isComponent = { - test(name: string): boolean { + test(name: Nullable): boolean { + if (name == null) { + return false; + } + return componentPrefixes.has(name.slice(0, 2)) && !name.includes(' ', 2); } }; diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index bff00428a3..f8c5296627 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -99,7 +99,7 @@ export function fillMeta( defaultProps = params.defaultProps !== false, canWatchProps = !SSR && !isRoot && !isFunctional; - Object.entries(meta.props).forEach(([name, prop]) => { + Object.entries(meta.props).forEach(([propName, prop]) => { if (prop == null) { return; } @@ -110,7 +110,7 @@ export function fillMeta( skipDefault = true; if (defaultProps || prop.forceDefault) { - const defaultInstanceValue = meta.instance[name]; + const defaultInstanceValue = meta.instance[propName]; skipDefault = false; getDefault = defaultInstanceValue; @@ -134,7 +134,7 @@ export function fillMeta( } if (!isRoot || defaultValue !== undefined) { - (prop.forceUpdate ? component.props : component.attrs)[name] = { + (prop.forceUpdate ? component.props : component.attrs)[propName] = { type: prop.type, required: prop.required !== false && defaultProps && defaultValue === undefined, @@ -148,8 +148,8 @@ export function fillMeta( } if (Object.size(prop.watchers) > 0) { - const watcherListeners = watchers[name] ?? []; - watchers[name] = watcherListeners; + const watcherListeners = watchers[propName] ?? []; + watchers[propName] = watcherListeners; prop.watchers.forEach((watcher) => { if ( @@ -164,21 +164,21 @@ export function fillMeta( } if (canWatchProps) { - const normalizedName = isBinding.test(name) ? isBinding.replace(name) : name; + const normalizedName = isBinding.test(propName) ? isBinding.replace(propName) : propName; if ((computedFields[normalizedName] ?? accessors[normalizedName]) != null) { const props = watchPropDependencies.get(normalizedName) ?? new Set(); - props.add(name); + props.add(propName); watchPropDependencies.set(normalizedName, props); } else { watchDependencies.forEach((deps, path) => { deps.some((dep) => { - if ((Object.isArray(dep) ? dep : dep.split('.', 1))[0] === name) { + if ((Object.isArray(dep) ? dep : dep.split('.', 1))[0] === propName) { const props = watchPropDependencies.get(path) ?? new Set(); - props.add(name); + props.add(propName); watchPropDependencies.set(path, props); return true; @@ -194,15 +194,15 @@ export function fillMeta( // Fields [meta.systemFields, meta.fields].forEach((field) => { - Object.entries(field).forEach(([name, field]) => { + Object.entries(field).forEach(([fieldName, field]) => { field?.watchers?.forEach((watcher) => { if (isFunctional && watcher.functional === false) { return; } - const watcherListeners = watchers[name] ?? []; + const watcherListeners = watchers[fieldName] ?? []; - watchers[name] = watcherListeners; + watchers[fieldName] = watcherListeners; watcherListeners.push(watcher); }); }); @@ -225,13 +225,13 @@ export function fillMeta( // Methods - Object.entries(methods).forEach(([name, method]) => { + Object.entries(methods).forEach(([methodName, method]) => { if (method == null) { return; } if (isFirstFill) { - component.methods[name] = wrapper; + component.methods[methodName] = wrapper; if (wrapper.length !== method.fn.length) { Object.defineProperty(wrapper, 'length', {get: () => method.fn.length}); @@ -239,17 +239,17 @@ export function fillMeta( } if (method.watchers != null) { - Object.entries(method.watchers).forEach(([name, watcher]) => { + Object.entries(method.watchers).forEach(([watcherName, watcher]) => { if (watcher == null || isFunctional && watcher.functional === false) { return; } - const watcherListeners = watchers[name] ?? []; - watchers[name] = watcherListeners; + const watcherListeners = watchers[watcherName] ?? []; + watchers[watcherName] = watcherListeners; watcherListeners.push({ ...watcher, - method: name, + method: methodName, args: Array.concat([], watcher.args), handler: Object.cast(method.fn) }); @@ -259,13 +259,13 @@ export function fillMeta( // Method hooks if (method.hooks) { - Object.entries(method.hooks).forEach(([name, hook]) => { + Object.entries(method.hooks).forEach(([hookName, hook]) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (hook == null || isFunctional && hook.functional === false) { return; } - hooks[name].push({...hook, fn: method.fn}); + hooks[hookName].push({...hook, fn: method.fn}); }); } @@ -280,7 +280,7 @@ export function fillMeta( if (isFirstFill) { const {mods} = component; - Object.entries(meta.mods).forEach(([name, mod]) => { + Object.entries(meta.mods).forEach(([modsName, mod]) => { let defaultValue: CanUndef; if (mod != null) { @@ -293,7 +293,7 @@ export function fillMeta( return false; }); - mods[name] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; + mods[modsName] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; } }); } diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 3dff5b0661..4c32cb2934 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -153,7 +153,7 @@ export function inheritMeta( // Modifiers inheritance - if (meta.params.partial != null) { + if (meta.params.partial == null) { inheritMods(meta, parentMeta); } From 8201a3809b0abaa104e2ecdb3665e57791923fac Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 13 Aug 2024 15:14:51 +0300 Subject: [PATCH 009/334] chore: fixed tests --- .../unit/lifecycle/initialization/initialization.ts | 7 +------ .../b-virtual-scroll-new/test/unit/scenario/reload.ts | 11 ++++------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/test/unit/lifecycle/initialization/initialization.ts b/src/components/base/b-virtual-scroll-new/test/unit/lifecycle/initialization/initialization.ts index 35d572afaa..17602e9c44 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/lifecycle/initialization/initialization.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/lifecycle/initialization/initialization.ts @@ -20,7 +20,6 @@ import type { VirtualScrollTestHelpers } from 'components/base/b-virtual-scroll- test.describe('', () => { let component: VirtualScrollTestHelpers['component'], - initLoadSpy: VirtualScrollTestHelpers['initLoadSpy'], provider: VirtualScrollTestHelpers['provider'], state: VirtualScrollTestHelpers['state']; @@ -37,7 +36,7 @@ test.describe('', () => { test.beforeEach(async ({demoPage, page}) => { await demoPage.goto(); - ({component, initLoadSpy, provider, state} = await createTestHelpers(page)); + ({component, provider, state} = await createTestHelpers(page)); await provider.start(); }); @@ -90,10 +89,6 @@ test.describe('', () => { ]); }); - test('should call `initLoad` once', async () => { - await test.expect(initLoadSpy.calls).resolves.toEqual([[]]); - }); - test('should end the component lifecycle', async () => { await test.expect(component.waitForLifecycleDone()).resolves.toBeUndefined(); }); diff --git a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts index f9df1162a6..e4436a1953 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts @@ -19,18 +19,17 @@ test.describe('', () => { let component: VirtualScrollTestHelpers['component'], provider: VirtualScrollTestHelpers['provider'], - state: VirtualScrollTestHelpers['state'], - initLoadSpy: VirtualScrollTestHelpers['initLoadSpy']; + state: VirtualScrollTestHelpers['state']; test.beforeEach(async ({demoPage, page}) => { await demoPage.goto(); - ({component, provider, state, initLoadSpy} = await createTestHelpers(page)); + ({component, provider, state} = await createTestHelpers(page)); await provider.start(); }); test.describe('`request` prop was changed', () => { - test('Should reset state and reload the component data', async () => { + test('should reset state and reload the component data', async ({page}) => { const chunkSize = [12, 20]; @@ -92,14 +91,13 @@ test.describe('', () => { 'renderDone' ]); - await test.expect(initLoadSpy.calls).resolves.toEqual([[], []]); await test.expect(component.waitForChildCountEqualsTo(chunkSize[1])).resolves.toBeUndefined(); }); }); ['reset', 'reset.silence', 'reset.load', 'reset.load.silence'].forEach((event, i) => { test.describe(`${event} fired`, () => { - test('Should reset state and reload the component data', async () => { + test('should reset state and reload the component data', async () => { const chunkSize = 12; @@ -157,7 +155,6 @@ test.describe('', () => { [[], [undefined, {silent: true}]] ]; - await test.expect(initLoadSpy.calls).resolves.toEqual(initLoadArgs[i]); await test.expect(component.waitForChildCountEqualsTo(chunkSize)).resolves.toBeUndefined(); }); }); From a31bf095f1df99c5e47a9b19007affb3a2d10a9d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 13 Aug 2024 15:23:56 +0300 Subject: [PATCH 010/334] chore: fixed eslint --- .../base/b-virtual-scroll-new/test/unit/scenario/reload.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts index e4436a1953..675f1f863b 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts @@ -29,7 +29,7 @@ test.describe('', () => { }); test.describe('`request` prop was changed', () => { - test('should reset state and reload the component data', async ({page}) => { + test('should reset state and reload the component data', async () => { const chunkSize = [12, 20]; @@ -95,7 +95,7 @@ test.describe('', () => { }); }); - ['reset', 'reset.silence', 'reset.load', 'reset.load.silence'].forEach((event, i) => { + ['reset', 'reset.silence', 'reset.load', 'reset.load.silence'].forEach((event) => { test.describe(`${event} fired`, () => { test('should reset state and reload the component data', async () => { const From afb89accd1f1e2b51556f76b3d4c45b40fc425d5 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 13 Aug 2024 18:02:31 +0300 Subject: [PATCH 011/334] refactor: better code --- .../component/decorators/component/index.ts | 64 +++++++++---------- src/core/component/meta/inherit.ts | 20 +++--- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index dc3cb60a91..b64f3e4ce4 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -25,7 +25,7 @@ import { } from 'core/component/const'; import { initEmitter } from 'core/component/event'; -import { createMeta, fillMeta, inheritMods, attachTemplatesToMeta, addMethodsToMeta } from 'core/component/meta'; +import { createMeta, fillMeta, inheritMods, inheritParams, attachTemplatesToMeta, addMethodsToMeta } from 'core/component/meta'; import { getComponentMods, getInfoFromConstructor } from 'core/component/reflect'; import { getComponent, ComponentEngine } from 'core/component/engines'; @@ -33,8 +33,7 @@ import { registerComponent, registerParentComponents } from 'core/component/init import type { ComponentConstructor, ComponentOptions } from 'core/component/interface'; -const - OVERRIDDEN = Symbol('This class is overridden'); +const OVERRIDDEN = Symbol('This class is overridden in the child layer'); /** * Registers a new component based on the tied class @@ -66,20 +65,20 @@ export function component(opts?: ComponentOptions): Function { const componentInfo = getInfoFromConstructor(target, opts), componentParams = componentInfo.params, - partOf = componentParams.partial != null; + isPartial = componentParams.partial != null; const componentOriginName = componentInfo.name, componentNormalizedName = componentInfo.componentName, - hasSameOrigin = !partOf && componentOriginName === componentInfo.parentParams?.name; + hasSameOrigin = !isPartial && componentOriginName === componentInfo.parentParams?.name; if (hasSameOrigin) { - Object.defineProperty(componentInfo.parent!, OVERRIDDEN, {value: true}); + Object.defineProperty(componentInfo.parent, OVERRIDDEN, {value: true}); } initEmitter.emit('bindConstructor', componentOriginName); - if (partOf) { + if (isPartial) { pushToInitList(() => { let meta = components.get(componentOriginName); @@ -88,9 +87,7 @@ export function component(opts?: ComponentOptions): Function { components.set(componentOriginName, meta); } - initEmitter.once(`constructor.${componentInfo.componentName}`, () => { - addMethodsToMeta(meta!, target); - }); + initEmitter.once(`constructor.${componentOriginName}`, addMethodsToMeta.bind(null, meta, target)); }); return; @@ -98,11 +95,16 @@ export function component(opts?: ComponentOptions): Function { pushToInitList(regComponent); - if (!Object.isTruly(componentOriginName) || componentParams.root === true || componentInfo.isAbstract) { + const needRegisterImmediate = + componentInfo.isAbstract || + componentParams.root === true || + !Object.isTruly(componentOriginName); + + if (needRegisterImmediate) { registerComponent(componentOriginName); } else { - requestIdleCallback(() => registerComponent(componentOriginName)); + requestIdleCallback(registerComponent.bind(null, componentOriginName)); } // If we have a smart component, @@ -115,13 +117,7 @@ export function component(opts?: ComponentOptions): Function { })(target); } - function pushToInitList(init: Function) { - const initList = componentRegInitializers[componentOriginName] ?? []; - componentRegInitializers[componentOriginName] = initList; - initList.push(init); - } - - function regComponent(): void { + function regComponent() { registerParentComponents(componentInfo); let rawMeta = components.get(componentNormalizedName); @@ -135,46 +131,44 @@ export function component(opts?: ComponentOptions): Function { rawMeta = Object.create(rawMeta, { constructor: { + configurable: true, enumerable: true, writable: true, - configurable: true, value: target }, mods: { + configurable: true, enumerable: true, writable: true, - configurable: true, value: newTarget ? getComponentMods(componentInfo) : rawMeta.mods }, params: { + configurable: true, enumerable: true, writable: true, - configurable: true, - value: { - ...componentInfo.parentParams, - ...componentInfo.params, - // eslint-disable-next-line deprecation/deprecation - deprecatedProps: {...componentInfo.parentParams?.deprecatedProps, ...componentInfo.params.deprecatedProps} - } + value: componentInfo.params } }); - if (newTarget && componentInfo.parentMeta != null) { - inheritMods(rawMeta!, componentInfo.parentMeta); + if (rawMeta != null && componentInfo.parentMeta != null) { + inheritParams(rawMeta, componentInfo.parentMeta); + + if (newTarget) { + inheritMods(rawMeta, componentInfo.parentMeta); + } } } const meta = rawMeta!; - components.set(componentOriginName, meta); if (componentParams.name == null || !componentInfo.isSmart) { components.set(target, meta); } - initEmitter.emit(`constructor.${componentInfo.componentName}`, { + initEmitter.emit(`constructor.${componentNormalizedName}`, { meta, parentMeta: componentInfo.parentMeta }); @@ -239,5 +233,11 @@ export function component(opts?: ComponentOptions): Function { } } } + + function pushToInitList(init: Function) { + const initList = componentRegInitializers[componentOriginName] ?? []; + componentRegInitializers[componentOriginName] = initList; + initList.push(init); + } }; } diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 4c32cb2934..08841c6b1d 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -24,7 +24,6 @@ export function inheritMeta( metaPointer = metaPointers[meta.componentName]; const { - params: pParams, props: pProps, fields: pFields, @@ -39,13 +38,7 @@ export function inheritMeta( // Component parameters inheritance - meta.params = { - ...pParams, - ...meta.params, - name: meta.params.name, - // eslint-disable-next-line deprecation/deprecation - deprecatedProps: {...pParams.deprecatedProps, ...meta.params.deprecatedProps} - }; + inheritParams(meta, parentMeta); // Watcher dependencies inheritance @@ -160,6 +153,17 @@ export function inheritMeta( return meta; } +export function inheritParams(meta: ComponentMeta, parentMeta: ComponentMeta): void { + meta.params = { + ...parentMeta.params, + ...meta.params, + name: meta.params.name, + + // eslint-disable-next-line deprecation/deprecation + deprecatedProps: {...parentMeta.params.deprecatedProps, ...meta.params.deprecatedProps} + }; +} + export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): void { const {mods: pMods} = parentMeta; From 7faa51d1a47ec57dd6287ab81447e92ef2e3f949 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 14:27:12 +0300 Subject: [PATCH 012/334] refactor: better code --- src/core/component/meta/fill.ts | 46 ++-- src/core/component/meta/inherit.ts | 293 ++++++++++----------- src/core/component/meta/interface/types.ts | 4 +- 3 files changed, 165 insertions(+), 178 deletions(-) diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index f8c5296627..7619c7eedf 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -17,9 +17,9 @@ import { addMethodsToMeta } from 'core/component/meta/method'; import type { ComponentConstructor, ComponentMeta, ModVal } from 'core/component/interface'; const - ALREADY_PASSED = Symbol('This target is passed'), - BLUEPRINT = Symbol('This is a meta blueprint'), - INSTANCE = Symbol('The component instance'); + BLUEPRINT = Symbol('The meta blueprint'), + INSTANCE = Symbol('The component instance'), + ALREADY_PASSED = Symbol('This constructor is already passed'); /** * Populates the passed metaobject with methods and properties from the specified component class constructor @@ -27,10 +27,7 @@ const * @param meta * @param [constructor] - the component constructor */ -export function fillMeta( - meta: ComponentMeta, - constructor: ComponentConstructor = meta.constructor -): ComponentMeta { +export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor = meta.constructor): ComponentMeta { addMethodsToMeta(meta, constructor); if (isAbstractComponent.test(meta.componentName)) { @@ -40,7 +37,7 @@ export function fillMeta( // For smart components, this method can be called more than once const isFirstFill = !constructor.hasOwnProperty(ALREADY_PASSED); - if (meta[BLUEPRINT] == null) { + if (Object.isDictionary(meta.params.functional) && meta[BLUEPRINT] == null) { Object.defineProperty(meta, BLUEPRINT, { value: { watchers: meta.watchers, @@ -49,17 +46,16 @@ export function fillMeta( }); } - const blueprint: Pick< - ComponentMeta, - 'watchers' | 'hooks' - > = meta[BLUEPRINT]; + const blueprint: CanNull> = meta[BLUEPRINT]; - Object.assign(meta, { - watchers: {...blueprint.watchers}, - hooks: Object.fromEntries( - Object.entries(blueprint.hooks).map(([key, val]) => [key, val.slice()]) - ) - }); + if (blueprint != null) { + Object.assign(meta, { + watchers: {...blueprint.watchers}, + hooks: Object.fromEntries( + Object.entries(blueprint.hooks).map(([key, val]) => [key, val.slice()]) + ) + }); + } const { component, @@ -115,8 +111,9 @@ export function fillMeta( skipDefault = false; getDefault = defaultInstanceValue; - // If the default value of a field is set through default values for a class property, - // then we need to clone it for each new component instance to ensure that they do not use a shared value + // If the default value of a field is set via default values for a class property, + // it is necessary to clone this value for each new component instance + // to ensure that they do not share the same value const needCloneDefValue = defaultInstanceValue != null && typeof defaultInstanceValue === 'object' && (!isTypeCanBeFunc(prop.type) || !Object.isFunction(defaultInstanceValue)); @@ -152,10 +149,7 @@ export function fillMeta( watchers[propName] = watcherListeners; prop.watchers.forEach((watcher) => { - if ( - isFunctional && watcher.functional === false || - !canWatchProps && !watcher.immediate - ) { + if (isFunctional && watcher.functional === false || !canWatchProps && !watcher.immediate) { return; } @@ -175,7 +169,9 @@ export function fillMeta( } else { watchDependencies.forEach((deps, path) => { deps.some((dep) => { - if ((Object.isArray(dep) ? dep : dep.split('.', 1))[0] === propName) { + const pathChunks = Object.isArray(dep) ? dep : dep.split('.', 1); + + if (pathChunks[0] === propName) { const props = watchPropDependencies.get(path) ?? new Set(); props.add(propName); diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 08841c6b1d..73289c387d 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -7,7 +7,9 @@ */ import { metaPointers, PARENT } from 'core/component/const'; -import type { ComponentMeta, ModDeclVal, FieldWatcher } from 'core/component/interface'; + +import type { ModDeclVal, FieldWatcher } from 'core/component/interface'; +import type { ComponentMeta } from 'core/component/meta/interface'; /** * Inherits the specified metaobject from another one. @@ -16,219 +18,208 @@ import type { ComponentMeta, ModDeclVal, FieldWatcher } from 'core/component/int * @param meta * @param parentMeta */ -export function inheritMeta( - meta: ComponentMeta, - parentMeta: ComponentMeta -): ComponentMeta { - const - metaPointer = metaPointers[meta.componentName]; - - const { - props: pProps, +export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): ComponentMeta { + const metaPointer = metaPointers[meta.componentName]; - fields: pFields, - tiedFields: pTiedFields, - computedFields: pComputedFields, - systemFields: pSystemFields, + Object.assign(meta.tiedFields, parentMeta.tiedFields); - accessors: pAccessors, - methods: pMethods, - watchDependencies: pWatchDependencies - } = parentMeta; - - // Component parameters inheritance + if (parentMeta.watchDependencies.size > 0) { + meta.watchDependencies = new Map(parentMeta.watchDependencies); + } inheritParams(meta, parentMeta); + inheritProp(meta.props, parentMeta.props); - // Watcher dependencies inheritance - - if (meta.watchDependencies.size > 0) { - pWatchDependencies.forEach((deps, path) => { - meta.watchDependencies.set(path, (meta.watchDependencies.get(path) ?? []).concat(deps)); - }); - - } else { - meta.watchDependencies = new Map(pWatchDependencies); - } + inheritField(meta.fields, parentMeta.fields); + inheritField(meta.systemFields, parentMeta.systemFields); - // Props/fields inheritance + inheritAccessors(meta.accessors, parentMeta.accessors); + inheritAccessors(meta.computedFields, parentMeta.computedFields); - { - const list = [ - [meta.props, pProps], - [meta.fields, pFields], - [meta.systemFields, pSystemFields] - ]; + inheritMethods(meta.methods, parentMeta.methods); - list.forEach(([store, parent]) => { - Object.entries(parent).forEach(([key, parent]) => { - if (parent == null) { - return; - } + if (meta.params.partial == null) { + inheritMods(meta, parentMeta); + } - if (metaPointer == null || !metaPointer[key]) { - store[key] = parent; - return; - } + return meta; - let - after: CanUndef>, - watchers: CanUndef>; + function inheritProp(current: ComponentMeta['props'], parent: ComponentMeta['props']) { + Object.entries(parent).forEach(([propName, parent]) => { + if (parent == null) { + return; + } - parent.watchers?.forEach((watcher: FieldWatcher) => { - watchers ??= new Map(); - watchers.set(watcher.handler, {...watcher}); - }); + if (metaPointer == null || !metaPointer[propName]) { + current[propName] = parent; + return; + } - if ('after' in parent) { - parent.after?.forEach((name: string) => { - after ??= new Set(); - after.add(name); - }); - } + let watchers: CanUndef>; - store[key] = {...parent, after, watchers}; + parent.watchers?.forEach((watcher: FieldWatcher) => { + watchers ??= new Map(); + watchers.set(watcher.handler, {...watcher}); }); + + current[propName] = {...parent, watchers}; }); } - // Tied fields inheritance + function inheritField(current: ComponentMeta['fields'], parent: ComponentMeta['fields']) { + Object.entries(parent).forEach(([fieldName, parent]) => { + if (parent == null) { + return; + } - Object.assign(meta.tiedFields, pTiedFields); + if (metaPointer == null || !metaPointer[fieldName]) { + current[fieldName] = parent; + return; + } - // Accessors inheritance + let + after: CanUndef>, + watchers: CanUndef>; - { - const list = [ - [meta.computedFields, pComputedFields], - [meta.accessors, pAccessors] - ]; + parent.watchers?.forEach((watcher: FieldWatcher) => { + watchers ??= new Map(); + watchers.set(watcher.handler, {...watcher}); + }); + + parent.after?.forEach((name: string) => { + after ??= new Set(); + after.add(name); + }); - list.forEach(([store, parentObj]) => { - Object.entries(parentObj).forEach(([key, parent]) => store[key] = {...parent!}); + current[fieldName] = {...parent, after, watchers}; }); } - // Methods inheritance - - const - {methods} = meta; - - Object.entries(pMethods).forEach(([key, parent]) => { - if (parent == null) { - return; - } - - if (metaPointer == null || !metaPointer[key]) { - methods[key] = {...parent}; - return; - } - - const - watchers = {}, - hooks = {}; + function inheritAccessors(current: ComponentMeta['accessors'], parent: ComponentMeta['accessors']) { + Object.entries(parent).forEach(([accessorName, parent]) => { + current[accessorName] = {...parent!}; + }); + } - if (parent.watchers != null) { - Object.entries(parent.watchers).forEach(([key, val]) => watchers[key] = {...val}); - } + function inheritMethods(current: ComponentMeta['methods'], parent: ComponentMeta['methods']) { + Object.entries(parent).forEach(([methodName, parent]) => { + if (parent == null) { + return; + } - if (parent.hooks != null) { - Object.entries(parent.hooks).forEach(([key, hook]) => { - hooks[key] = { - ...hook, - after: Object.size(hook.after) > 0 ? new Set(hook.after) : undefined - }; - }); - } + if (metaPointer == null || !metaPointer[methodName]) { + current[methodName] = {...parent}; + return; + } - methods[key] = {...parent, watchers, hooks}; - }); + const + watchers = {}, + hooks = {}; - // Modifiers inheritance + if (parent.watchers != null) { + Object.entries(parent.watchers).forEach(([key, val]) => { + watchers[key] = {...val}; + }); + } + + if (parent.hooks != null) { + Object.entries(parent.hooks).forEach(([key, hook]) => { + hooks[key] = { + ...hook, + after: Object.size(hook.after) > 0 ? new Set(hook.after) : undefined + }; + }); + } - if (meta.params.partial == null) { - inheritMods(meta, parentMeta); + current[methodName] = {...parent, watchers, hooks}; + }); } - - return meta; } +/** + * Inherits the `params` property for a given metaobject based on the parent one. + * This function modifies the original object. + * + * @param meta + * @param parentMeta + */ export function inheritParams(meta: ComponentMeta, parentMeta: ComponentMeta): void { + /* eslint-disable deprecation/deprecation */ + + const + deprecatedProps = meta.params.deprecatedProps ?? {}, + parentDeprecatedProps = parentMeta.params.deprecatedProps; + meta.params = { ...parentMeta.params, ...meta.params, - name: meta.params.name, - // eslint-disable-next-line deprecation/deprecation - deprecatedProps: {...parentMeta.params.deprecatedProps, ...meta.params.deprecatedProps} + deprecatedProps, + name: meta.params.name }; + + if (parentDeprecatedProps != null && Object.keys(parentDeprecatedProps).length > 0) { + meta.params.deprecatedProps = {...parentDeprecatedProps, ...deprecatedProps}; + } + + /* eslint-enable deprecation/deprecation */ } +/** + * Inherits the `mods` property for a given metaobject based on the parent one. + * This function modifies the original object. + * + * @param meta + * @param parentMeta + */ export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): void { - const {mods: pMods} = parentMeta; - const {mods} = meta; - Object.keys(pMods).forEach((name) => { + Object.entries(parentMeta.mods).forEach(([modName, parentModValues]) => { const - currentMods = mods[name], - parentMods = (pMods[name] ?? []).slice(); + currentModValues = mods[modName], + forkedParentModValues = parentModValues?.slice() ?? []; - if (currentMods != null) { + if (currentModValues != null) { const values = Object.createDict(); - currentMods.slice().forEach((val, i, mods) => { - if (val !== PARENT) { - if (Object.isArray(val) || !(val in values)) { - values[String(val)] = Object.cast(val); + currentModValues.forEach((modVal, i) => { + if (modVal !== PARENT) { + const modName = String(modVal); + + if (Object.isArray(modVal) || !(modName in values)) { + values[modName] = >modVal; } return; } - let - hasDefault = false; + const hasDefault = currentModValues.some((el) => Object.isArray(el)); - for (let i = 0; i < mods.length; i++) { - const - el = mods[i]; + let appliedDefault = !hasDefault; - if (Object.isArray(el)) { - hasDefault = true; - break; - } - } + forkedParentModValues.forEach((modVal) => { + const modsName = String(modVal); - let - parentDef = !hasDefault; - - parentMods.forEach((val) => { - if (!(val in values)) { - values[String(val)] = Object.cast(val); + if (!(modsName in values)) { + values[modsName] = >modVal; } - if (!parentDef && Object.isArray(val)) { - parentMods[i] = val[0]; - parentDef = true; + if (!appliedDefault && Object.isArray(modVal)) { + forkedParentModValues[i] = modVal[0]; + appliedDefault = true; } }); - currentMods.splice(i, 1, ...parentMods); - }); - - const - valuesList: ModDeclVal[] = []; - - Object.values(values).forEach((val) => { - if (val !== undefined) { - valuesList.push(val); - } + currentModValues.splice(i, 1, ...forkedParentModValues); }); - mods[name] = valuesList; + mods[modName] = Object + .values(values) + .filter((val) => val !== undefined); - } else if (!(name in mods)) { - mods[name] = parentMods; + } else if (!(modName in mods)) { + mods[modName] = forkedParentModValues; } }); } diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index c8785a76b8..87a335addd 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -13,9 +13,9 @@ import type { PropOptions, InitFieldFn, MergeFieldFn, UniqueFieldFn } from 'core import type { ComponentInterface, FieldWatcher, MethodWatcher, Hook } from 'core/component/interface'; export interface ComponentProp extends PropOptions { - watchers: Map; forceUpdate: boolean; forceDefault?: boolean; + watchers?: Map; default?: unknown; meta: Dictionary; } @@ -34,8 +34,8 @@ export interface ComponentSystemField extends ComponentSystemField { - watchers?: Map; forceUpdate?: boolean; + watchers?: Map; } export type ComponentAccessorCacheType = From 95514d641d12317e546fcfcae94a72f362bceee3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 14:27:46 +0300 Subject: [PATCH 013/334] refactor: better code --- .../component/decorators/component/index.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index b64f3e4ce4..e3ef7bfbbd 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -24,11 +24,23 @@ import { } from 'core/component/const'; -import { initEmitter } from 'core/component/event'; -import { createMeta, fillMeta, inheritMods, inheritParams, attachTemplatesToMeta, addMethodsToMeta } from 'core/component/meta'; -import { getComponentMods, getInfoFromConstructor } from 'core/component/reflect'; +import { + + createMeta, + fillMeta, + + inheritMods, + inheritParams, + attachTemplatesToMeta, + addMethodsToMeta + +} from 'core/component/meta'; + +import { initEmitter } from 'core/component/event'; import { getComponent, ComponentEngine } from 'core/component/engines'; + +import { getComponentMods, getInfoFromConstructor } from 'core/component/reflect'; import { registerComponent, registerParentComponents } from 'core/component/init'; import type { ComponentConstructor, ComponentOptions } from 'core/component/interface'; From c3d3c1e3000e8162e91671812245420f0da30601 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 14:31:33 +0300 Subject: [PATCH 014/334] fix: added assertion --- src/core/component/meta/fill.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 7619c7eedf..71a61b33ae 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -144,7 +144,7 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor } } - if (Object.size(prop.watchers) > 0) { + if (prop.watchers != null && Object.size(prop.watchers) > 0) { const watcherListeners = watchers[propName] ?? []; watchers[propName] = watcherListeners; From d35fd467a299c3bc5a7a9b8a5c7c6b0d5039d4c3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 14:53:25 +0300 Subject: [PATCH 015/334] refactor: better code --- src/core/component/meta/fill.ts | 12 ++++++++---- src/core/component/meta/fork.ts | 18 ++++++++---------- src/core/component/meta/method.ts | 3 +-- src/core/component/meta/tpl.ts | 3 +-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 71a61b33ae..57879b9187 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -49,11 +49,15 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor const blueprint: CanNull> = meta[BLUEPRINT]; if (blueprint != null) { + const hooks = {}; + + Object.entries(blueprint.hooks).forEach(([name, handlers]) => { + hooks[name] = handlers.slice(); + }); + Object.assign(meta, { - watchers: {...blueprint.watchers}, - hooks: Object.fromEntries( - Object.entries(blueprint.hooks).map(([key, val]) => [key, val.slice()]) - ) + hooks, + watchers: {...blueprint.watchers} }); } diff --git a/src/core/component/meta/fork.ts b/src/core/component/meta/fork.ts index 31abbd950a..4967085907 100644 --- a/src/core/component/meta/fork.ts +++ b/src/core/component/meta/fork.ts @@ -13,24 +13,22 @@ import type { ComponentMeta } from 'core/component/interface'; * @param base */ export function forkMeta(base: ComponentMeta): ComponentMeta { - const - meta = Object.create(base); + const meta = Object.create(base); meta.watchDependencies = new Map(meta.watchDependencies); meta.params = Object.create(base.params); - meta.watchers = {}; + meta.hooks = {}; - Object.entries(base.hooks).forEach(([key, hooks]) => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (hooks != null) { - meta.hooks[key] = hooks.slice(); - } + Object.entries(base.hooks).forEach(([name, handlers]) => { + meta.hooks[name] = handlers.slice(); }); - Object.entries(base.watchers).forEach(([key, watchers]) => { + meta.watchers = {}; + + Object.entries(base.watchers).forEach(([name, watchers]) => { if (watchers != null) { - meta.watchers[key] = watchers.slice(); + meta.watchers[name] = watchers.slice(); } }); diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index a21dd7abe4..628c89a688 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -9,8 +9,7 @@ import { defProp } from 'core/const/props'; import type { ComponentMeta } from 'core/component/interface'; -const - ALREADY_PASSED = Symbol('This target is passed'); +const ALREADY_PASSED = Symbol('This target is passed'); /** * Loops through the prototype of the passed component constructor and diff --git a/src/core/component/meta/tpl.ts b/src/core/component/meta/tpl.ts index c53f913afe..17eefced81 100644 --- a/src/core/component/meta/tpl.ts +++ b/src/core/component/meta/tpl.ts @@ -16,8 +16,7 @@ import type { ComponentMeta } from 'core/component/interface'; * @param [templates] - a dictionary containing the registered templates */ export function attachTemplatesToMeta(meta: ComponentMeta, templates?: Dictionary): void { - const - {methods, methods: {render}} = meta; + const {methods, methods: {render}} = meta; // We have a custom render function if (render != null && !render.wrapper) { From 55e3f590d7d4c5e2ed06fc13a0dc19617d6a73c8 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 14:53:44 +0300 Subject: [PATCH 016/334] chore: fixed eslint --- .../base/b-virtual-scroll-new/test/unit/scenario/reload.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts index 675f1f863b..89ee723ba7 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts @@ -148,13 +148,6 @@ test.describe('', () => { 'renderDone' ]); - const initLoadArgs = [ - [[], []], - [[], [undefined, {silent: true}]], - [[], []], - [[], [undefined, {silent: true}]] - ]; - await test.expect(component.waitForChildCountEqualsTo(chunkSize)).resolves.toBeUndefined(); }); }); From d7ee8bea9413bb9acaf49e4b96a70fec2ee0dc34 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 15:08:36 +0300 Subject: [PATCH 017/334] docs: added missing doc --- .../component/decorators/component/README.md | 31 ++++++++++++++++--- src/core/component/meta/README.md | 14 +++++++-- src/core/component/meta/interface/options.ts | 21 +++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/core/component/decorators/component/README.md b/src/core/component/decorators/component/README.md index e53e462c20..af71ee0e46 100644 --- a/src/core/component/decorators/component/README.md +++ b/src/core/component/decorators/component/README.md @@ -18,7 +18,7 @@ export default class bUser extends iBlock { ## How to create a component? To register a new component, generate a simple JS/TS class and apply the `@component` decorator to it. -You also have the option to pass additional parameters to this decorator. +You can also pass additional parameters to this decorator. For instance, a component can be established as functional. ```typescript @@ -38,8 +38,8 @@ export default class bUser extends iBlock { The `@component` decorator gathers information from other nested decorators within the class. Through the use of reflection, this decorator then constructs a unique structure of the [[ComponentMeta]] type. -Following this, the formed structure is transferred to the utilized component library adapter, -leading to the creation of an actual component. +Following this, the constructed structure is transferred to the component library adapter in use, +resulting in the creation of an actual component. ## Additional options @@ -81,6 +81,29 @@ class pRoot extends iStaticPage { } ``` +### [partial] + +The name of the component to which this one belongs. +This option is used when we want to split the component into multiple classes. + +Please note that in partial classes, +there should be no conflicts in methods or properties with other partial classes of this component. + +```typescript +import iBlock, { component, prop } from 'components/super/i-block/i-block'; + +@component({partial: 'bExample'}) +class bExampleProps extends iBlock { + @prop({type: Number}) + value: number = 0; +} + +@component() +class bExample extends bExampleProps { + +} +``` + ### [tpl = `true`] If set to false, the component uses the default loopback render function instead of loading its own template. @@ -98,7 +121,7 @@ There are several options available for this parameter: depending on the values of its props: 1. If an empty dictionary is passed, the component will always be created as a functional one. - However, you still have the option to create it like a regular component using the `v-func` directive. + However, you can still create it like a regular component using the `v-func` directive. ``` < b-button v-func = false diff --git a/src/core/component/meta/README.md b/src/core/component/meta/README.md index ddd1b3c11d..f4f1d0cf49 100644 --- a/src/core/component/meta/README.md +++ b/src/core/component/meta/README.md @@ -97,12 +97,12 @@ export interface ComponentMeta { watchers: Dictionary; /** - * A dictionary that contains the component dependencies to watch in order to invalidate the cache of computed fields + * A dictionary that contains the component dependencies to watch to invalidate the cache of computed fields */ watchDependencies: ComponentWatchDependencies; /** - * A dictionary that contains the component prop dependencies to watch in order + * A dictionary that contains the component prop dependencies to watch * to invalidate the cache of computed fields */ watchPropDependencies: ComponentWatchPropDependencies; @@ -188,6 +188,16 @@ Forks the metaobject of the passed component and returns the copy. Inherits the specified metaobject from another one. This function modifies the original object and returns it. +### inheritParams + +Inherits the `params` property for a given metaobject based on the parent one. +This function modifies the original object. + +### inheritMods + +Inherits the `mods` property for a given metaobject based on the parent one. +This function modifies the original object. + ### fillMeta Populates the passed metaobject with methods and properties from the specified component class constructor. diff --git a/src/core/component/meta/interface/options.ts b/src/core/component/meta/interface/options.ts index 7967abe169..45768e3000 100644 --- a/src/core/component/meta/interface/options.ts +++ b/src/core/component/meta/interface/options.ts @@ -32,6 +32,27 @@ export interface ComponentOptions { */ name?: string; + /** + * The name of the component to which this one belongs. + * This option is used when we want to split the component into multiple classes. + * + * Please note that in partial classes, + * there should be no conflicts in methods or properties with other partial classes of this component. + * + * @example + * ```typescript + * @component({partial: 'bExample'}) + * class bExampleProps extends iBlock { + * @prop({type: Number}) + * value: number = 0; + * } + * + * @component() + * class bExample extends bExampleProps { + * + * } + * ``` + */ partial?: string; /** From 9d5a811cc24364253c7a4a6feba2579d0afd23cc Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 15:09:28 +0300 Subject: [PATCH 018/334] chore: stylish fixes --- src/components/base/b-bottom-slide/props.ts | 2 +- src/components/base/b-list/props.ts | 2 +- src/components/base/b-router/props.ts | 2 +- src/components/base/b-slider/props.ts | 2 +- src/components/base/b-tree/props.ts | 2 +- src/components/base/b-virtual-scroll-new/handlers.ts | 2 +- src/components/base/b-virtual-scroll-new/props.ts | 2 +- src/components/form/b-button/props.ts | 2 +- src/components/form/b-form/props.ts | 2 +- src/components/form/b-select/props.ts | 2 +- src/components/super/i-block/base/index.ts | 2 +- src/components/super/i-block/event/index.ts | 2 +- src/components/super/i-block/friends.ts | 2 +- src/components/super/i-block/mods/index.ts | 2 +- src/components/super/i-block/props.ts | 2 +- src/components/super/i-block/providers/index.ts | 2 +- src/components/super/i-block/state/index.ts | 2 +- src/components/super/i-data/data.ts | 6 +----- src/components/super/i-data/handlers.ts | 6 +----- src/components/super/i-input/fields.ts | 2 +- src/components/super/i-input/modules/handlers.ts | 2 +- src/components/super/i-input/props.ts | 2 +- 22 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/components/base/b-bottom-slide/props.ts b/src/components/base/b-bottom-slide/props.ts index 7db52af174..c1a41723fd 100644 --- a/src/components/base/b-bottom-slide/props.ts +++ b/src/components/base/b-bottom-slide/props.ts @@ -13,7 +13,7 @@ import iBlock, { component, prop } from 'components/super/i-block/i-block'; import { heightMode } from 'components/base/b-bottom-slide/const'; import type { HeightMode } from 'components/base/b-bottom-slide/interface'; -@component({partial: 'b-bottom-slide'}) +@component({partial: 'bBottomSlide'}) export default abstract class iBottomSlideProps extends iBlock { /** * Component height mode: diff --git a/src/components/base/b-list/props.ts b/src/components/base/b-list/props.ts index 29280c9720..9d1179a0e8 100644 --- a/src/components/base/b-list/props.ts +++ b/src/components/base/b-list/props.ts @@ -14,7 +14,7 @@ import iData, { prop, component } from 'components/super/i-data/i-data'; import type { Item } from 'components/base/b-list/b-list'; -@component({partial: 'b-list'}) +@component({partial: 'bList'}) export default abstract class iListProps extends iData { /** {@link iActiveItems.Item} */ readonly Item!: Item; diff --git a/src/components/base/b-router/props.ts b/src/components/base/b-router/props.ts index 9fc6b91ebb..21589b4446 100644 --- a/src/components/base/b-router/props.ts +++ b/src/components/base/b-router/props.ts @@ -14,7 +14,7 @@ import iData, { component, prop } from 'components/super/i-data/i-data'; import type { StaticRoutes, RouteOption } from 'components/base/b-router/interface'; import type bRouter from 'components/base/b-router/b-router'; -@component({partial: 'b-router'}) +@component({partial: 'bRouter'}) export default abstract class iRouterProps extends iData { /** * Type: page parameters diff --git a/src/components/base/b-slider/props.ts b/src/components/base/b-slider/props.ts index be43f85e71..ad873f2d72 100644 --- a/src/components/base/b-slider/props.ts +++ b/src/components/base/b-slider/props.ts @@ -12,7 +12,7 @@ import iData, { component, prop } from 'components/super/i-data/i-data'; import { sliderModes, alignTypes } from 'components/base/b-slider/const'; import type { Mode, AlignType } from 'components/base/b-slider/interface'; -@component({partial: 'b-slider'}) +@component({partial: 'bSlider'}) export default abstract class iSliderProps extends iData { /** {@link iItems.Item} */ readonly Item!: object; diff --git a/src/components/base/b-tree/props.ts b/src/components/base/b-tree/props.ts index b7bda4375c..734d555899 100644 --- a/src/components/base/b-tree/props.ts +++ b/src/components/base/b-tree/props.ts @@ -17,7 +17,7 @@ import type { Item, LazyRender, RenderFilter } from 'components/base/b-tree/inte import type AsyncRender from 'components/friends/async-render'; import type { TaskFilter } from 'components/friends/async-render'; -@component({partial: 'b-tree'}) +@component({partial: 'bTree'}) export default abstract class iTreeProps extends iData { /** {@link iItems.Item} */ readonly Item!: Item; diff --git a/src/components/base/b-virtual-scroll-new/handlers.ts b/src/components/base/b-virtual-scroll-new/handlers.ts index 95536093d6..cd2503a956 100644 --- a/src/components/base/b-virtual-scroll-new/handlers.ts +++ b/src/components/base/b-virtual-scroll-new/handlers.ts @@ -24,7 +24,7 @@ const $$ = symbolGenerator(); * A class that provides an API to handle events emitted by the {@link bVirtualScrollNew} component. * This class is designed to work in conjunction with {@link bVirtualScrollNew}. */ -@component({partial: 'b-virtual-scroll-new'}) +@component({partial: 'bVirtualScrollNew'}) export abstract class iVirtualScrollHandlers extends iVirtualScrollProps { /** * Handler: component reset event. diff --git a/src/components/base/b-virtual-scroll-new/props.ts b/src/components/base/b-virtual-scroll-new/props.ts index a94b970507..a7b974c89c 100644 --- a/src/components/base/b-virtual-scroll-new/props.ts +++ b/src/components/base/b-virtual-scroll-new/props.ts @@ -32,7 +32,7 @@ import type { Observer } from 'components/base/b-virtual-scroll-new/modules/obse import iData, { component, prop } from 'components/super/i-data/i-data'; -@component({partial: 'b-virtual-scroll-new'}) +@component({partial: 'bVirtualScrollNew'}) export default abstract class iVirtualScrollProps extends iData { /** {@link iItems.item} */ readonly Item!: object; diff --git a/src/components/form/b-button/props.ts b/src/components/form/b-button/props.ts index a745d0081a..1c1698eaab 100644 --- a/src/components/form/b-button/props.ts +++ b/src/components/form/b-button/props.ts @@ -25,7 +25,7 @@ import iData, { import type bButton from 'components/form/b-button/b-button'; import type { ButtonType } from 'components/form/b-button/interface'; -@component({partial: 'b-button'}) +@component({partial: 'bButton'}) export default abstract class iButtonProps extends iData { override readonly dataProviderProp: DataProviderProp = 'Provider'; override readonly defaultRequestFilter: RequestFilter = true; diff --git a/src/components/form/b-form/props.ts b/src/components/form/b-form/props.ts index 37208afcdd..37a4565dab 100644 --- a/src/components/form/b-form/props.ts +++ b/src/components/form/b-form/props.ts @@ -22,7 +22,7 @@ import iData, { import type { ActionFn } from 'components/form/b-form/interface'; -@component({partial: 'b-form'}) +@component({partial: 'bForm'}) export default abstract class iFormProps extends iData { override readonly dataProviderProp: DataProviderProp = 'Provider'; override readonly defaultRequestFilter: RequestFilter = true; diff --git a/src/components/form/b-select/props.ts b/src/components/form/b-select/props.ts index 77d186f646..6d134c9c14 100644 --- a/src/components/form/b-select/props.ts +++ b/src/components/form/b-select/props.ts @@ -13,7 +13,7 @@ import type iActiveItems from 'components/traits/i-active-items/i-active-items'; import iInputText, { component, prop } from 'components/super/i-input-text/i-input-text'; import type { Value, FormValue, Item } from 'components/form/b-select/interface'; -@component({partial: 'b-select'}) +@component({partial: 'bSelect'}) export default abstract class iSelectProps extends iInputText { override readonly Value!: Value; diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 178e4c38bb..9f429d59d5 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -59,7 +59,7 @@ import iBlockFriends from 'components/super/i-block/friends'; const $$ = symbolGenerator(); -@component({partial: 'i-block'}) +@component({partial: 'iBlock'}) export default abstract class iBlockBase extends iBlockFriends { override readonly Component!: iBlock; override readonly Root!: iStaticPage; diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index daf9d4e632..c409952de0 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -36,7 +36,7 @@ export * from 'components/super/i-block/event/interface'; const $$ = symbolGenerator(); -@component({partial: 'i-block'}) +@component({partial: 'iBlock'}) export default abstract class iBlockEvent extends iBlockBase { /** * Associative type for typing events emitted by the component. diff --git a/src/components/super/i-block/friends.ts b/src/components/super/i-block/friends.ts index e2372c0f78..6fc6fbadac 100644 --- a/src/components/super/i-block/friends.ts +++ b/src/components/super/i-block/friends.ts @@ -35,7 +35,7 @@ import { system, hook } from 'components/super/i-block/decorators'; import iBlockProps from 'components/super/i-block/props'; -@component({partial: 'i-block'}) +@component({partial: 'iBlock'}) export default abstract class iBlockFriends extends iBlockProps { /** * A class with methods to provide component classes/styles to another component, etc diff --git a/src/components/super/i-block/mods/index.ts b/src/components/super/i-block/mods/index.ts index 4aa43c5c41..e464c0662d 100644 --- a/src/components/super/i-block/mods/index.ts +++ b/src/components/super/i-block/mods/index.ts @@ -21,7 +21,7 @@ import iBlockEvent from 'components/super/i-block/event'; export * from 'components/super/i-block/mods/interface'; -@component({partial: 'i-block'}) +@component({partial: 'iBlock'}) export default abstract class iBlockMods extends iBlockEvent { @system({merge: mergeMods, init: initMods}) override readonly mods!: ModsDict; diff --git a/src/components/super/i-block/props.ts b/src/components/super/i-block/props.ts index 212f5cfff2..e70a30ae13 100644 --- a/src/components/super/i-block/props.ts +++ b/src/components/super/i-block/props.ts @@ -16,7 +16,7 @@ import type { ModsProp } from 'components/super/i-block/modules/mods'; import { prop, DecoratorMethodWatcher } from 'components/super/i-block/decorators'; import type { TransitionMethod } from 'components/base/b-router/interface'; -@component({partial: 'i-block'}) +@component({partial: 'iBlock'}) export default abstract class iBlockProps extends ComponentInterface { @prop({type: String, required: false}) override readonly componentIdProp?: string; diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 633eccbe9d..539c02ce67 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -39,7 +39,7 @@ export * from 'components/super/i-block/providers/interface'; const $$ = symbolGenerator(); -@component({partial: 'i-block'}) +@component({partial: 'iBlock'}) export default abstract class iBlockProviders extends iBlockState { /** {@link iBlock.dontWaitRemoteProvidersProp} */ @system((o) => o.sync.link((val) => { diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index d3c60cd3fd..b299eafd77 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -33,7 +33,7 @@ import type { Stage, ComponentStatus, ComponentStatuses } from 'components/super import iBlockMods from 'components/super/i-block/mods'; import type { Theme } from 'core/theme-manager'; -@component({partial: 'i-block'}) +@component({partial: 'iBlock'}) export default abstract class iBlockState extends iBlockMods { /** * A list of additional dependencies to load during the component's initialization diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index 08ee035d90..b0c81a20d9 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -43,11 +43,7 @@ const interface iDataData extends Trait {} -@component({ - partial: 'i-data', - functional: null -}) - +@component({partial: 'iData'}) @derive(iDataProvider) abstract class iDataData extends iBlock implements iDataProvider { /** diff --git a/src/components/super/i-data/handlers.ts b/src/components/super/i-data/handlers.ts index a6fd837f38..4ee2a608e7 100644 --- a/src/components/super/i-data/handlers.ts +++ b/src/components/super/i-data/handlers.ts @@ -20,11 +20,7 @@ import type { RequestParams, RetryRequestFn } from 'components/super/i-data/inte const $$ = symbolGenerator(); -@component({ - partial: 'i-data', - functional: null -}) - +@component({partial: 'iData'}) export default abstract class iDataHandlers extends iDataData { protected override initGlobalEvents(resetListener?: boolean): void { super.initGlobalEvents(resetListener != null ? resetListener : Boolean(this.dataProvider)); diff --git a/src/components/super/i-input/fields.ts b/src/components/super/i-input/fields.ts index d1d0bbb902..d48a036495 100644 --- a/src/components/super/i-input/fields.ts +++ b/src/components/super/i-input/fields.ts @@ -26,7 +26,7 @@ import type { } from 'components/super/i-input/interface'; -@component({partial: 'i-input'}) +@component({partial: 'iInput'}) export default abstract class iInputFields extends iInputProps { /** * A list of component value(s) that cannot be submitted via the associated form diff --git a/src/components/super/i-input/modules/handlers.ts b/src/components/super/i-input/modules/handlers.ts index 06e9b60234..58d0c3123d 100644 --- a/src/components/super/i-input/modules/handlers.ts +++ b/src/components/super/i-input/modules/handlers.ts @@ -13,7 +13,7 @@ import { component, ModEvent } from 'components/super/i-data/i-data'; import iInputFields from 'components/super/i-input/fields'; -@component({partial: 'i-input'}) +@component({partial: 'iInput'}) export default abstract class iInputHandlers extends iInputFields { protected override initRemoteData(): CanUndef> { if (this.db == null) { diff --git a/src/components/super/i-input/props.ts b/src/components/super/i-input/props.ts index e89fbfe5d5..2cc13d70df 100644 --- a/src/components/super/i-input/props.ts +++ b/src/components/super/i-input/props.ts @@ -14,7 +14,7 @@ import type { Value, FormValue, Validator } from 'components/super/i-input/inter import { unpackIf } from 'components/super/i-input/modules/helpers'; -@component({partial: 'i-input'}) +@component({partial: 'iInput'}) export default abstract class iInputProps extends iData { /** * Type: the value of the component From c27a4cc53619fff48b08f285711e415137ad2ad0 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 15:16:09 +0300 Subject: [PATCH 019/334] chore: improved jsdoc --- src/core/component/const/cache.ts | 21 ++++++++++++--------- src/core/component/const/enums.ts | 8 ++++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/core/component/const/cache.ts b/src/core/component/const/cache.ts index be8c558cb9..79e6c4ee2b 100644 --- a/src/core/component/const/cache.ts +++ b/src/core/component/const/cache.ts @@ -25,44 +25,47 @@ import type { } from 'core/component/interface'; /** - * A dictionary with the component declaration parameters + * A dictionary containing the component declaration parameters */ export const componentParams = new Map(); /** - * A dictionary with the component declaration parameters + * A dictionary containing the component declaration parameters that are declared as partial. */ export const partialInfo = new Map(); /** - * A dictionary with the registered root components + * A dictionary containing the registered root components */ export const rootComponents = Object.createDict>>(); /** - * A dictionary with the registered components + * A dictionary containing the registered components */ export const components = new Map(); /** - * A dictionary with the registered component initializers. - * By default, components are not registered automatically, but only upon the component's first call from a template. + * A dictionary containing the registered component initializers. + * + * By default, components are not registered automatically; + * they are only registered upon the component's first call from a template or when idle. + * * This dictionary contains functions to register components. */ export const componentRegInitializers = Object.createDict(); /** - * A dictionary with the registered component render factories + * A dictionary containing the registered component render factories */ export const componentRenderFactories = Object.createDict(); /** - * A dictionary with component pointers for metatables + * A dictionary containing component pointers for metatables */ export const metaPointers = Object.createDict>(); /** - * Global initialized application (not supported in SSR) + * Globally initialized application (not supported in SSR) */ export const app: App = { context: null, diff --git a/src/core/component/const/enums.ts b/src/core/component/const/enums.ts index e4c778b5d7..0e1ffa3afd 100644 --- a/src/core/component/const/enums.ts +++ b/src/core/component/const/enums.ts @@ -7,7 +7,7 @@ */ /** - * A dictionary with names of hooks that occur before a component is created + * A dictionary containing the names of hooks that occur before a component is created */ export const beforeHooks = Object.createDict({ beforeRuntime: true, @@ -16,7 +16,7 @@ export const beforeHooks = Object.createDict({ }); /** - * A dictionary with names of hooks that occur before a component is mounted + * A dictionary containing the names of hooks that occur before a component is mounted */ export const beforeMountHooks = Object.createDict({ ...beforeHooks, @@ -25,7 +25,7 @@ export const beforeMountHooks = Object.createDict({ }); /** - * A dictionary with names of hooks that occur before a component is rendered or re-rendered + * A dictionary containing the names of hooks that occur before a component is rendered or re-rendered */ export const beforeRenderHooks = Object.createDict({ ...beforeMountHooks, @@ -33,7 +33,7 @@ export const beforeRenderHooks = Object.createDict({ }); /** - * A dictionary with names of hooks that occur after a component is destroyed + * A dictionary containing the names of hooks that occur after a component is destroyed */ export const destroyedHooks = Object.createDict({ beforeDestroy: true, From 3f8f6b3be4f8f9dc6074e00afae554c62223296c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 14 Aug 2024 17:51:43 +0300 Subject: [PATCH 020/334] fix: restored slice --- src/core/component/meta/inherit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 73289c387d..c78e7ee0ad 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -183,7 +183,7 @@ export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): voi if (currentModValues != null) { const values = Object.createDict(); - currentModValues.forEach((modVal, i) => { + currentModValues.slice().forEach((modVal, i) => { if (modVal !== PARENT) { const modName = String(modVal); From 9ab624325817ce829dec3b1b7931ad6e951373f8 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 15 Aug 2024 15:03:19 +0300 Subject: [PATCH 021/334] feat: added a feature of forever cached values --- src/core/component/accessor/README.md | 2 +- src/core/component/accessor/index.ts | 24 ++++---- src/core/component/decorators/factory.ts | 59 +++++++++---------- .../decorators/interface/accessor.ts | 8 ++- src/core/component/meta/interface/types.ts | 19 +++++- src/core/component/meta/method.ts | 10 +++- 6 files changed, 71 insertions(+), 51 deletions(-) diff --git a/src/core/component/accessor/README.md b/src/core/component/accessor/README.md index 0ac2a8c1d8..91d0dc2191 100644 --- a/src/core/component/accessor/README.md +++ b/src/core/component/accessor/README.md @@ -2,7 +2,7 @@ This module provides an API to initialize component accessors and computed fields into a component instance. -## What differences between accessors and computed fields? +## What Differences Between Accessors and Computed Fields? A computed field is an accessor that can have its value cached or be watched for changes. To enable value caching, you can use the `@computed` decorator when defining or overriding your accessor. diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index e8b3e5643e..e9ef385ca0 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -96,8 +96,9 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { Object.entries(meta.computedFields).forEach(([name, computed]) => { const canSkip = + computed == null || component[name] != null || - computed == null || computed.cache === 'auto' || + computed.cache === 'auto' || !SSR && isFunctional && computed.functional === false; if (canSkip) { @@ -108,11 +109,15 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const get = function get(this: typeof component): unknown { const {hook} = this; - // We should not use the getter's cache until the component is fully created, - // because until that moment, we cannot track changes to dependent entities - // and reset the cache when they change. + // If an accessor is set with `cache: true` but dependencies are not explicitly or implicitly specified, + // then this field will be cached without the ability to reset the cache + const canUseForeverCache = computed.dependencies == null && computed.tiedWith == null; + + // We should not use the getter's cache until the component is fully created. + // Because until that moment, we cannot track changes to dependent entities and reset the cache when they change. // This can lead to hard-to-detect errors. - const canUseCache = beforeHooks[hook] == null; + // Please note that in case of forever caching, we cache immediately. + const canUseCache = canUseForeverCache || beforeHooks[hook] == null; if (canUseCache && cacheStatus in get) { // If a getter already has a cached result and is used inside a template, @@ -121,7 +126,9 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { // but the template is not. // To avoid this problem, we explicitly touch all dependent entities. // For functional components, this problem does not exist, as no change in state can trigger their re-render. - if (!isFunctional && hook !== 'created') { + const needEffect = !canUseForeverCache && !isFunctional && hook !== 'created'; + + if (needEffect) { meta.watchDependencies.get(name)?.forEach((path) => { // @ts-ignore (effect) void this[path]; @@ -142,10 +149,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const value = computed.get!.call(this); - // Due-to-context inheritance in functional components, - // we should not cache the computed value until the component is created - // @see https://github.com/V4Fire/Client/issues/1292 - if (!SSR && (canUseCache || !isFunctional)) { + if (canUseForeverCache || !SSR && (canUseCache || !isFunctional)) { cachedAccessors.add(get); get[cacheStatus] = value; } diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index c5e405eaeb..4ea10a0477 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -25,7 +25,7 @@ import type { } from 'core/component/decorators/interface'; /** - * Factory to create component property decorators + * Factory for creating component property decorators * * @param cluster - the property cluster to decorate, like `fields` or `systemFields` * @param [transformer] - a transformer for the passed decorator parameters @@ -142,7 +142,7 @@ export function paramsFactory( }); } else { - hooks[hook] = wrapOpts({name, hook}); + hooks[hook] = wrapOpts({name, hook}); } }); } @@ -199,8 +199,7 @@ export function paramsFactory( p = transformer(p, metaKey); } - const - info = metaCluster[key] ?? {src: meta.componentName}; + const info = metaCluster[key] ?? {src: meta.componentName}; let { watchers, @@ -263,42 +262,38 @@ export function paramsFactory( } function inheritFromParent() { - const - invertedMetaKeys = invertedFieldMap[metaKey]; - - if (invertedMetaKeys != null) { - for (let i = 0; i < invertedMetaKeys.length; i++) { - const - invertedMetaKey = invertedMetaKeys[i], - invertedMetaCluster = meta[invertedMetaKey]; - - if (key in invertedMetaCluster) { - const info = {...invertedMetaCluster[key]}; - delete info.functional; - - if (invertedMetaKey === 'prop') { - if (Object.isFunction(info.default)) { - (info).init = info.default; - delete info.default; - } - - } else if (metaKey === 'prop') { - delete (info).init; - } + const invertedMetaKeys: CanUndef = invertedFieldMap[metaKey]; - metaCluster[key] = info; - delete invertedMetaCluster[key]; + invertedMetaKeys?.some((invertedMetaKey) => { + const invertedMetaCluster = meta[invertedMetaKey]; + + if (key in invertedMetaCluster) { + const info = {...invertedMetaCluster[key]}; + delete info.functional; + + if (invertedMetaKey === 'prop') { + if (Object.isFunction(info.default)) { + (info).init = info.default; + delete info.default; + } - break; + } else if (metaKey === 'prop') { + delete (info).init; } + + metaCluster[key] = info; + delete invertedMetaCluster[key]; + + return true; } - } + + return false; + }); } } function wrapOpts(opts: T): T { - const - p = meta.params; + const p = meta.params; // eslint-disable-next-line eqeqeq if (opts.functional === undefined && p.functional === null) { diff --git a/src/core/component/decorators/interface/accessor.ts b/src/core/component/decorators/interface/accessor.ts index 85048e89e2..18e1343687 100644 --- a/src/core/component/decorators/interface/accessor.ts +++ b/src/core/component/decorators/interface/accessor.ts @@ -12,12 +12,14 @@ import type { DecoratorFunctionalOptions } from 'core/component/decorators/inter export interface DecoratorComponentAccessor extends DecoratorFunctionalOptions { /** * If set to true, the accessor value will be cached after the first touch. + * * The option is set to true by default if it also provided `dependencies` or the bound accessor matches * by the name with another prop or field. + * * If the option value is passed as `auto` caching will be delegated to the used component library. * * Also, when an accessor has a logically related prop/field (using the naming convention - * "${property} -> ${property}Prop | ${property}Store") we don't need to add additional dependencies. + * "${property} → ${property}Prop | ${property}Store") we don't need to add additional dependencies. * * @example * ```typescript @@ -83,10 +85,10 @@ export interface DecoratorComponentAccessor extends DecoratorFunctionalOptions { /** * A list of dependencies for the accessor. - * The dependencies are needed to watch for the accessor mutations or to invalidate its cache. + * The dependencies are necessary to watch for the accessor mutations or to invalidate its cache. * * Also, when an accessor has a logically related prop/field (using the naming convention - * "${property} -> ${property}Prop | ${property}Store") we don't need to add additional dependencies. + * "${property} → ${property}Prop | ${property}Store") we don't need to add additional dependencies. * * @example * ```typescript diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index 87a335addd..2fbb9ccd4d 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -10,6 +10,7 @@ import type { WatchPath } from 'core/object/watch'; import type { WritableComputedOptions, DirectiveBinding } from 'core/component/engines'; import type { PropOptions, InitFieldFn, MergeFieldFn, UniqueFieldFn } from 'core/component/decorators'; + import type { ComponentInterface, FieldWatcher, MethodWatcher, Hook } from 'core/component/interface'; export interface ComponentProp extends PropOptions { @@ -22,15 +23,19 @@ export interface ComponentProp extends PropOptions { export interface ComponentSystemField { src: string; + meta: Dictionary; + atom?: boolean; + after?: Set; + default?: unknown; unique?: boolean | UniqueFieldFn; + functional?: boolean; functionalWatching?: boolean; - after?: Set; + init?: InitFieldFn; merge?: MergeFieldFn | boolean; - meta: Dictionary; } export interface ComponentField extends ComponentSystemField { @@ -45,25 +50,33 @@ export type ComponentAccessorCacheType = export interface ComponentAccessor extends Partial> { src: string; cache: ComponentAccessorCacheType; + functional?: boolean; watchable?: boolean; + + dependencies?: WatchPath[]; + tiedWith?: ComponentProp | ComponentField; } export interface ComponentMethod { fn: Function; + src?: string; wrapper?: boolean; functional?: boolean; + watchers?: Dictionary; hooks?: ComponentMethodHooks; } export interface ComponentHook { fn: Function; + name?: string; + after?: Set; + functional?: boolean; once?: boolean; - after?: Set; } export type ComponentHooks = { diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index 628c89a688..a236c46d7a 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -61,12 +61,14 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me propKey = `${name}Prop`, storeKey = `${name}Store`; - let metaKey: string; + let + metaKey: string, + tiedWith: CanUndef; // Computed fields are cached by default if ( name in computedFields || - !(name in accessors) && (props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) + !(name in accessors) && (tiedWith = props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) ) { metaKey = 'computedFields'; @@ -131,6 +133,10 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me get: desc.get ?? old?.get, set }); + + if (tiedWith != null) { + store[name].tiedWith = tiedWith; + } } }); } From 86880287df162ead71d5459f8e947daecee57204 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 15 Aug 2024 17:11:20 +0300 Subject: [PATCH 022/334] feat: added a new `forever` cache --- src/core/component/accessor/index.ts | 4 +--- src/core/component/meta/interface/types.ts | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index e9ef385ca0..0e299f5a46 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -109,9 +109,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const get = function get(this: typeof component): unknown { const {hook} = this; - // If an accessor is set with `cache: true` but dependencies are not explicitly or implicitly specified, - // then this field will be cached without the ability to reset the cache - const canUseForeverCache = computed.dependencies == null && computed.tiedWith == null; + const canUseForeverCache = computed.cache === 'forever'; // We should not use the getter's cache until the component is fully created. // Because until that moment, we cannot track changes to dependent entities and reset the cache when they change. diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index 2fbb9ccd4d..d43e616e66 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -45,6 +45,7 @@ export interface ComponentField extends Partial> { From 1aba01f421dcaabcc9936c9c7ddfe2a22880d968 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 15 Aug 2024 17:26:43 +0300 Subject: [PATCH 023/334] chore: stylish fixes & optimization --- src/core/component/hook/index.ts | 69 ++++++++++++++--------- src/core/component/method/index.ts | 18 ++---- src/core/component/queue-emitter/index.ts | 10 ++-- src/core/component/watch/component-api.ts | 3 +- 4 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/core/component/hook/index.ts b/src/core/component/hook/index.ts index 5e0e7fa07f..c9f28b8915 100644 --- a/src/core/component/hook/index.ts +++ b/src/core/component/hook/index.ts @@ -75,42 +75,59 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn } default: { - const - emitter = new QueueEmitter(), - filteredHooks: ComponentHook[] = []; - - hooks.forEach((hook) => { + if (hooks.some((hook) => hook.after != null && hook.after.size > 0)) { const - nm = hook.name; + emitter = new QueueEmitter(), + filteredHooks: ComponentHook[] = []; - if (!hook.once) { - filteredHooks.push(hook); - } + hooks.forEach((hook) => { + const nm = hook.name; - emitter.on(hook.after, () => { - const - res = args.length > 0 ? hook.fn.apply(component, args) : hook.fn.call(component); - - if (Object.isPromise(res)) { - return res.then(() => nm != null ? emitter.emit(nm) : undefined); + if (!hook.once) { + filteredHooks.push(hook); } - const - tasks = nm != null ? emitter.emit(nm) : null; + emitter.on(hook.after, () => { + const res = args.length > 0 ? hook.fn.apply(component, args) : hook.fn.call(component); - if (tasks != null) { - return tasks; - } + if (Object.isPromise(res)) { + return res.then(() => nm != null ? emitter.emit(nm) : undefined); + } + + const tasks = nm != null ? emitter.emit(nm) : null; + + if (tasks != null) { + return tasks; + } + }); }); - }); - m.hooks[hook] = filteredHooks; + m.hooks[hook] = filteredHooks; - const - tasks = emitter.drain(); + const tasks = emitter.drain(); + + if (Object.isPromise(tasks)) { + return tasks; + } + + } else { + const tasks: Array> = []; + + hooks.slice().forEach((hook) => { + const res = args.length > 0 ? hook.fn.apply(component, args) : hook.fn.call(component); + + if (hook.once) { + hooks.pop(); + } - if (Object.isPromise(tasks)) { - return tasks; + if (Object.isPromise(res)) { + tasks.push(res); + } + }); + + if (tasks.length > 0) { + return Promise.all(tasks).then(() => undefined); + } } } } diff --git a/src/core/component/method/index.ts b/src/core/component/method/index.ts index 88139ad437..d04d8575c6 100644 --- a/src/core/component/method/index.ts +++ b/src/core/component/method/index.ts @@ -23,8 +23,7 @@ export function attachMethodsFromMeta(component: ComponentInterface): void { meta: {methods} } = component.unsafe; - const - isFunctional = meta.params.functional === true; + const isFunctional = meta.params.functional === true; Object.entries(methods).forEach(([name, method]) => { if (method == null || !SSR && isFunctional && method.functional === false) { @@ -53,20 +52,13 @@ export function attachMethodsFromMeta(component: ComponentInterface): void { * ``` */ export function callMethodFromComponent(component: ComponentInterface, method: string, ...args: unknown[]): void { - const - obj = component.unsafe.meta.methods[method]; + const obj = component.unsafe.meta.methods[method]; if (obj != null) { - try { - const - res = obj.fn.apply(component, args); + const res = obj.fn.apply(component, args); - if (Object.isPromise(res)) { - res.catch(stderr); - } - - } catch (err) { - stderr(err); + if (Object.isPromise(res)) { + res.catch(stderr); } } } diff --git a/src/core/component/queue-emitter/index.ts b/src/core/component/queue-emitter/index.ts index b9d1e80bc7..73b628479a 100644 --- a/src/core/component/queue-emitter/index.ts +++ b/src/core/component/queue-emitter/index.ts @@ -14,6 +14,7 @@ import type { EventListener } from 'core/component/queue-emitter/interface'; export * from 'core/component/queue-emitter/interface'; + export default class QueueEmitter { /** * A queue of event handlers that are ready to be executed @@ -74,8 +75,7 @@ export default class QueueEmitter { ev.delete(event); if (ev.size === 0) { - const - task = el.handler(); + const task = el.handler(); if (Object.isPromise(task)) { tasks.push(task); @@ -94,11 +94,9 @@ export default class QueueEmitter { * the method will return a promise that will only be resolved once all internal promises are resolved. */ drain(): CanPromise { - const - {queue} = this; + const {queue} = this; - const - tasks: Array> = []; + const tasks: Array> = []; queue.forEach((el) => { const diff --git a/src/core/component/watch/component-api.ts b/src/core/component/watch/component-api.ts index 8f2b699c11..c3feeee4a0 100644 --- a/src/core/component/watch/component-api.ts +++ b/src/core/component/watch/component-api.ts @@ -349,8 +349,7 @@ export function implementComponentWatchAPI(component: ComponentInterface): void return; } - const - rootKey = String(info.path[0]); + const rootKey = String(info.path[0]); // If there has been changed properties that can affect memoized computed fields, // then we need to invalidate these caches From 9c1c7092f55657e06b1682979d62d10044e25a61 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 15 Aug 2024 17:41:50 +0300 Subject: [PATCH 024/334] fix: forever cache support --- src/core/component/decorators/factory.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index 4ea10a0477..ab7b5f1b52 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -80,6 +80,7 @@ export function paramsFactory( } else if ( p.cache === true || p.cache === 'auto' || + p.cache === 'forever' || p.cache !== false && (Object.isArray(p.dependencies) || key in meta.computedFields) ) { metaKey = 'computedFields'; From 120e3784371fd7d7bcc454bfddd3ec422aa01e9b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 15 Aug 2024 18:13:06 +0300 Subject: [PATCH 025/334] chore: use cached getters insted of system --- package.json | 2 +- src/components/super/i-block/friends.ts | 282 ++++++++------------ src/components/super/i-block/state/index.ts | 8 + yarn.lock | 10 +- 4 files changed, 126 insertions(+), 176 deletions(-) diff --git a/package.json b/package.json index 97f8749429..e163ed768a 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "merge2": "1.4.1", "mini-css-extract-plugin": "2.5.3", "monic": "2.6.1", - "monic-loader": "3.0.4", + "monic-loader": "3.0.5", "nanoid": "3.2.0", "nib": "1.1.2", "node-static": "0.7.11", diff --git a/src/components/super/i-block/friends.ts b/src/components/super/i-block/friends.ts index 6fc6fbadac..342fd99c29 100644 --- a/src/components/super/i-block/friends.ts +++ b/src/components/super/i-block/friends.ts @@ -7,8 +7,8 @@ */ import * as browser from 'core/browser'; -import * as presets from 'components/presets'; import * as helpers from 'core/helpers'; +import * as presets from 'components/presets'; import Daemons, { DaemonsDict } from 'components/friends/daemons'; import Analytics from 'components/friends/analytics'; @@ -31,63 +31,35 @@ import State from 'components/friends/state'; import Storage from 'components/friends/storage'; import { component } from 'core/component'; -import { system, hook } from 'components/super/i-block/decorators'; +import { system, hook, computed } from 'components/super/i-block/decorators'; import iBlockProps from 'components/super/i-block/props'; @component({partial: 'iBlock'}) export default abstract class iBlockFriends extends iBlockProps { /** - * A class with methods to provide component classes/styles to another component, etc - */ - @system({ - atom: true, - unique: true, - init: (ctx) => new Provide(Object.cast(ctx)) - }) - - readonly provide!: Provide; - - /** - * An API for collecting and rendering various component information + * An API for providing component classes, styles and other related properties to another component */ - @system({ - atom: true, - unique: true, - init: (ctx) => new InfoRender(ctx) - }) - - readonly infoRender!: InfoRender; + @computed({cache: 'forever'}) + get provide(): Provide { + return new Provide(Object.cast(this)); + } /** - * A class with helper methods for safely accessing component/object properties + * An API for safely accessing the component's properties or any other object * * @example * ```js * this.field.get('foo.bar.bla') * ``` */ - @system({ - atom: true, - unique: true, - init: (ctx) => new Field(Object.cast(ctx)) - }) - - readonly field!: Field; - - /** - * A class to send component analytic events - */ - @system({ - atom: true, - unique: true, - init: (ctx) => new Analytics(Object.cast(ctx)) - }) - - readonly analytics!: Analytics; + @computed({cache: 'forever'}) + get field(): Field { + return new Field(Object.cast(this)); + } /** - * An API to synchronize fields and props of the component + * An API for synchronizing fields and props of the component * * @example * ```typescript @@ -101,16 +73,13 @@ export default abstract class iBlockFriends extends iBlockProps { * } * ``` */ - @system({ - atom: true, - unique: true, - init: (ctx) => new Sync(Object.cast(ctx)) - }) - - readonly sync!: Sync; + @computed({cache: 'forever'}) + get sync(): Sync { + return new Sync(Object.cast(this)); + } /** - * A class to render component fragments asynchronously + * An API for rendering component fragments asynchronously * * @example * ``` @@ -118,182 +87,147 @@ export default abstract class iBlockFriends extends iBlockProps { * {{ el }} * ``` */ - @system({ - atom: true, - unique: true, - init: (ctx) => new AsyncRender(Object.cast(ctx)) - }) - - readonly asyncRender!: AsyncRender; + @computed({cache: 'forever'}) + get asyncRender(): AsyncRender { + return new AsyncRender(Object.cast(this)); + } /** - * A class for low-level working with a component VDOM tree + * An API for low-level working with a component VDOM tree */ - @system({ - atom: true, - unique: true, - init: (ctx) => new VDOM(Object.cast(ctx)) - }) - - readonly vdom!: VDOM; + @computed({cache: 'forever'}) + get vdom(): VDOM { + return new VDOM(Object.cast(this)); + } /** - * A class with helper methods to work with the component life cycle + * An API for collecting and rendering various component information */ - @system({ - atom: true, - unique: true, - init: (ctx) => new Lfc(Object.cast(ctx)) - }) - - readonly lfc!: Lfc; + @computed({cache: 'forever'}) + get infoRender(): InfoRender { + return new InfoRender(Object.cast(this)); + } /** - * A dictionary with component daemons + * An API for sending component analytic events */ - static readonly daemons: DaemonsDict = {}; + @computed({cache: 'forever'}) + get analytics(): Analytics { + return new Analytics(Object.cast(this)); + } /** - * A class to create daemons associated with a component + * An API for working with the component life cycle */ - @system({ - unique: true, - init: (ctx) => new Daemons(Object.cast(ctx)) - }) + @computed({cache: 'forever'}) + get lfc(): Lfc { + return new Lfc(Object.cast(this)); + } - protected readonly daemons!: Daemons; + /** + * A dictionary containing component daemons + */ + static readonly daemons: DaemonsDict = {}; /** - * An API to work with a component in terms of [BEM](https://en.bem.info/methodology/quick-start/) + * An API for working with the component in terms of [BEM](https://en.bem.info/methodology/quick-start/) */ - @system({unique: true}) protected block?: Block; /** - * A class for low-level working with a component DOM tree + * An API for low-level working with a component DOM tree */ - @system({ - atom: true, - unique: true, - init: (ctx) => new DOM(Object.cast(ctx)) - }) - - protected readonly dom!: DOM; + @computed({cache: 'forever'}) + protected get dom(): DOM { + return new DOM(Object.cast(this)); + } /** - * A class for persistent storage of component data + * An API for persistent storage of component data */ - @system({ - atom: true, - unique: true, - init: (ctx) => new Storage(Object.cast(ctx)) - }) - - protected readonly storage!: Storage; + @computed({cache: 'forever'}) + protected get storage(): Storage { + return new Storage(Object.cast(this)); + } /** - * A class with methods to initialize a component state from various related sources + * An API for initializing the component state from various related sources */ - @system({ - atom: true, - unique: true, - init: (ctx) => new State(Object.cast(ctx)) - }) - - protected readonly state!: State; + @computed({cache: 'forever'}) + protected get state(): State { + return new State(Object.cast(this)); + } /** - * A class to manage dynamically loaded modules + * An API for managing dynamically loaded modules */ - @system({ - atom: true, - unique: true, - init: (ctx) => new ModuleLoader(Object.cast(ctx)) - }) - - protected readonly moduleLoader!: ModuleLoader; + @computed({cache: 'forever'}) + protected get moduleLoader(): ModuleLoader { + return new ModuleLoader(Object.cast(this)); + } /** - * A cache dictionary for the `opt.ifOnce` method + * An API for creating daemons associated with the component */ - @system({merge: true}) - protected readonly ifOnceStore: Dictionary = {}; + @computed({cache: 'forever'}) + protected get daemons(): Daemons { + return new Daemons(Object.cast(this)); + } /** - * A class with helper methods to optimize component rendering + * A cache dictionary for the `opt.ifOnce` method */ - @system({ - atom: true, - unique: true, - init: (ctx) => new Opt(Object.cast(ctx)) - }) - - protected readonly opt!: Opt; + @system({merge: true, init: () => ({})}) + protected readonly ifOnceStore!: Dictionary; /** - * An API to determine the current browser name/version + * An API containing helper methods for optimizing and profiling component rendering */ - @system({ - atom: true, - unique: true - }) - - protected readonly browser: typeof browser = browser; + @computed({cache: 'forever'}) + protected get opt(): Opt { + return new Opt(Object.cast(this)); + } /** - * A dictionary with component presets + * An API for determining the current browser name/version */ - @system({ - atom: true, - unique: true, - init: () => presets - }) - - protected readonly presets!: typeof presets; + @computed({cache: 'forever'}) + protected get browser(): typeof browser { + return browser; + } /** - * A dictionary containing a set of helper functions - * that can be utilized within the component template to extend its functionality + * A dictionary containing component presets */ - @system({ - atom: true, - unique: true - }) - - protected readonly h: typeof helpers = helpers; + @computed({cache: 'forever'}) + protected get presets(): typeof presets { + return presets; + } /** - * An API for working with the target document's URL + * A dictionary containing a set of helper functions + * that can be used within the component template to extend its functionality */ - @system({ - atom: true, - unique: true, - init: (ctx) => ctx.remoteState.location - }) - - protected readonly location!: Location; + @computed({cache: 'forever'}) + protected get h(): typeof helpers { + return helpers; + } /** * A link to the global object */ - @system({ - atom: true, - unique: true, - init: () => globalThis - }) - - protected readonly global!: Window; + @computed({cache: 'forever'}) + protected get global(): Window { + return Object.cast(globalThis); + } /** * A link to the native `console` API */ - @system({ - atom: true, - unique: true, - init: () => console - }) - - protected readonly console!: Console; + @computed({cache: 'forever'}) + protected get console(): Console { + return console; + } /** * Initializes the process of collecting debugging information for the component @@ -302,4 +236,12 @@ export default abstract class iBlockFriends extends iBlockProps { protected initInfoRender(): void { this.infoRender.initDataGathering(); } + + /** + * Initializes the component daemons + */ + @hook('beforeCreate') + protected initDaemons(): void { + this.daemons.init(); + } } diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index b299eafd77..76010d60a3 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -333,6 +333,14 @@ export default abstract class iBlockState extends iBlockMods { */ protected hookStore: Hook = 'beforeRuntime'; + /** + * An API for working with the target document's URL + */ + @computed({cache: 'forever'}) + protected get location(): URL { + return this.remoteState.location; + } + /** * Switches the component to a new lifecycle hook * diff --git a/yarn.lock b/yarn.lock index 2e8066e486..670a89fd53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5671,7 +5671,7 @@ __metadata: merge2: "npm:1.4.1" mini-css-extract-plugin: "npm:2.5.3" monic: "npm:2.6.1" - monic-loader: "npm:3.0.4" + monic-loader: "npm:3.0.5" nanoid: "npm:3.2.0" nib: "npm:1.1.2" node-static: "npm:0.7.11" @@ -18540,16 +18540,16 @@ __metadata: languageName: node linkType: hard -"monic-loader@npm:3.0.4": - version: 3.0.4 - resolution: "monic-loader@npm:3.0.4" +"monic-loader@npm:3.0.5": + version: 3.0.5 + resolution: "monic-loader@npm:3.0.5" dependencies: collection.js: "npm:^6.7.11" loader-utils: "npm:^2.0.0" peerDependencies: monic: ^2.0.0 webpack: "*" - checksum: 9f43b6a3a87932979ca5b61119a0d4cc4940f2990ce14619f4a2a5e4ced42a8a010feba932278a9b8a5f9a5880c521ed268f1c6f807784d4c6ab6d43bd1991ba + checksum: 69a4555c843b2dd44673a506aaba1940860e8e9ee8dce76880b3ef9492aaaff904ecd7c6c4904d06aaa657d8c9e40e39f4c4c3b0ab3b2090b394eadac2e12974 languageName: node linkType: hard From df13c5c9c099dceed7eec6ac117f5a8f96f6a972 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 15 Aug 2024 18:16:58 +0300 Subject: [PATCH 026/334] fix: fixed override --- src/components/base/b-cache-ssr/b-cache-ssr.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/base/b-cache-ssr/b-cache-ssr.ts b/src/components/base/b-cache-ssr/b-cache-ssr.ts index b68940d032..c5944d5a4c 100644 --- a/src/components/base/b-cache-ssr/b-cache-ssr.ts +++ b/src/components/base/b-cache-ssr/b-cache-ssr.ts @@ -34,18 +34,16 @@ export default class bCacheSSR extends iBlock { return `${this.globalName}-default`; } - @system((ctx) => { + @system(() => ssrCache) + protected override readonly $ssrCache!: AbstractCache; + + protected override get state(): SuperState { class State extends SuperState { override initFromStorage(): CanPromise { return false; } } - return new State(ctx); - }) - - protected override readonly state!: SuperState; - - @system(() => ssrCache) - protected override readonly $ssrCache!: AbstractCache; + return new State(this); + } } From a1765177c7488e5e8b76c553d6a276348b7b6ab1 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 15 Aug 2024 18:33:28 +0300 Subject: [PATCH 027/334] refactor: prefer computed --- src/components/super/i-block/base/index.ts | 78 ++++++------- src/components/super/i-block/event/index.ts | 123 ++++++++------------ 2 files changed, 84 insertions(+), 117 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 9f429d59d5..a6af56eb2e 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -61,21 +61,25 @@ const @component({partial: 'iBlock'}) export default abstract class iBlockBase extends iBlockFriends { - override readonly Component!: iBlock; - override readonly Root!: iStaticPage; - override readonly $root!: this['Root']; + /** @inheritDoc */ + declare readonly Component: iBlock; + + /** @inheritDoc */ + declare readonly Root: iStaticPage; + + /** @inheritDoc */ + declare readonly $root: this['Root']; @system({ atom: true, + unique: (ctx, oldCtx) => !ctx.$el?.classList.contains(oldCtx.componentId), init: (o) => { - const - {r} = o; + const {r} = o; - let - id = o.componentIdProp; + let id = o.componentIdProp; if (id != null) { if (!($$.propIds in r)) { @@ -136,7 +140,6 @@ export default abstract class iBlockBase extends iBlockFriends { /** * True if the component is a functional component */ - @computed() get isFunctional(): boolean { return this.meta.params.functional === true; } @@ -150,7 +153,7 @@ export default abstract class iBlockBase extends iBlockFriends { } /** - * A dictionary with additional attributes for the component's root element + * A dictionary containing additional attributes for the component's root element */ get rootAttrs(): Dictionary { return this.field.get('rootAttrsStore')!; @@ -181,8 +184,8 @@ export default abstract class iBlockBase extends iBlockFriends { * A list of `blockReady` listeners. * This is used to optimize component initialization. */ - @system({unique: true}) - protected blockReadyListeners: Function[] = []; + @system({unique: true, init: () => []}) + protected readonly blockReadyListeners!: Function[]; /** * A temporary cache dictionary. @@ -199,8 +202,8 @@ export default abstract class iBlockBase extends iBlockFriends { * A temporary cache dictionary. * Mutation of this object can cause the component to re-render. */ - @field({merge: true}) - protected reactiveTmp: Dictionary = {}; + @field({merge: true, init: () => ({})}) + protected reactiveTmp!: Dictionary; /** * A cache dictionary of watched values @@ -213,22 +216,20 @@ export default abstract class iBlockBase extends iBlockFriends { protected watchCache!: Dictionary; /** - * A dictionary with additional attributes for the component's root element + * A dictionary containing additional attributes for the component's root element * {@link iBlock.rootAttrsStore} */ - @field() - protected rootAttrsStore: Dictionary = {}; + @field({init: () => ({})}) + protected rootAttrsStore!: Dictionary; /** * A list of keyset names used to internationalize the component */ - @system({atom: true, unique: true}) - protected componentI18nKeysets: string[] = (() => { - const - res: string[] = []; + @computed({cache: 'forever'}) + protected get componentI18nKeysets(): string[] { + const res: string[] = []; - let - keyset: CanUndef = getComponentName(this.constructor); + let keyset: CanUndef = getComponentName(this.constructor); while (keyset != null) { res.push(keyset); @@ -236,12 +237,11 @@ export default abstract class iBlockBase extends iBlockFriends { } return res; - })(); + } /** * A link to the component itself */ - @computed() protected get self(): this { return this; } @@ -260,7 +260,7 @@ export default abstract class iBlockBase extends iBlockFriends { } /** - * Sets a watcher to the component/object property or event by the specified path. + * Sets a watcher to the component/object property or event at the specified path. * * When you observe changes to certain properties, * the event handler function can accept a second argument that references the old value of the property. @@ -323,8 +323,8 @@ export default abstract class iBlockBase extends iBlockFriends { * Also, if you are listening to an event, you can control when to start listening to the event by using special * characters at the beginning of the path string: * - * 1. `'!'` - start listening to an event on the "beforeCreate" hook, e.g.: `'!rootEmitter:reset'`; - * 2. `'?'` - start listening to an event on the "mounted" hook, e.g.: `'?$el:click'`. + * 1. `'!'` - start listening to an event on the "beforeCreate" hook, e.g., `'!rootEmitter:reset'`; + * 2. `'?'` - start listening to an event on the "mounted" hook, e.g., `'?$el:click'`. * * By default, all events start listening on the "created" hook. * @@ -381,7 +381,7 @@ export default abstract class iBlockBase extends iBlockFriends { ): void; /** - * Sets a watcher to the component property/event by the specified path + * Sets a watcher to the component property/event at the specified path * * @param path - a path to the component property to watch or an event to listen * @param handler @@ -444,8 +444,7 @@ export default abstract class iBlockBase extends iBlockFriends { return; } - const - {async: $a} = this; + const {async: $a} = this; let handler: RawWatchHandler, @@ -519,8 +518,7 @@ export default abstract class iBlockBase extends iBlockFriends { */ nextTick(opts?: AsyncOptions): Promise; nextTick(fnOrOpts?: BoundFn | AsyncOptions, opts?: AsyncOptions): CanPromise { - const - {async: $a} = this; + const {async: $a} = this; if (Object.isFunction(fnOrOpts)) { this.$nextTick($a.proxy(Object.cast>(fnOrOpts), opts)); @@ -577,8 +575,7 @@ export default abstract class iBlockBase extends iBlockFriends { return; } - let - resolvedContext: Array; + let resolvedContext: Array; if (this.globalName != null) { resolvedContext = ['component:global', this.globalName, context, this.componentName]; @@ -610,8 +607,7 @@ export default abstract class iBlockBase extends iBlockFriends { @hook('mounted') protected initBlockInstance(): void { if (this.block != null) { - const - {node} = this.block; + const {node} = this.block; if (node == null || node === this.$el) { return; @@ -626,13 +622,12 @@ export default abstract class iBlockBase extends iBlockFriends { this.block = new Block(Object.cast(this)); if (this.blockReadyListeners.length > 0) { - this.blockReadyListeners.forEach((listener) => listener()); - this.blockReadyListeners = []; + this.blockReadyListeners.splice(0, this.blockReadyListeners.length).forEach((listener) => listener()); } } /** - * Initializes remote watchers from `watchProp` + * Initializes remote watchers from the `watchProp` prop */ @hook({beforeDataCreate: {functional: false}}) protected initRemoteWatchers(): void { @@ -660,10 +655,7 @@ export default abstract class iBlockBase extends iBlockFriends { Object.defineProperty(this, 'app', { enumerable: true, configurable: true, - - get() { - return 'app' in this.r ? this.r['app'] : undefined; - } + get: () => 'app' in this.r ? this.r['app'] : undefined }); } } diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index c409952de0..ef91d53678 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -21,7 +21,7 @@ import type { AsyncOptions, EventEmitterWrapper, ReadonlyEventEmitterWrapper, Ev import { component, globalEmitter, ComponentEmitterOptions } from 'core/component'; -import { system, hook, watch } from 'components/super/i-block/decorators'; +import { computed, hook, watch } from 'components/super/i-block/decorators'; import { initGlobalListeners } from 'components/super/i-block/modules/listeners'; import type iBlock from 'components/super/i-block/i-block'; @@ -39,7 +39,7 @@ const @component({partial: 'iBlock'}) export default abstract class iBlockEvent extends iBlockBase { /** - * Associative type for typing events emitted by the component. + * An associative type for typing events emitted by the component. * Events are described using tuples, where the first element is the event name, and the rest are arguments. */ readonly SelfEmitter!: InferComponentEvents; /** - * Associative type for typing events emitted by the `localEmitter`. + * An associative type for typing events emitted by the `localEmitter`. * Events are described using tuples, where the first element is the event name, and the rest are arguments. */ readonly LocalEmitter!: {}; @@ -85,31 +85,29 @@ export default abstract class iBlockEvent extends iBlockBase { * this.selfEmitter.off({group: 'example'}); * ``` */ - @system({ - atom: true, - unique: true, - init: (o, d) => (d.async).wrapEventEmitter({ + @computed({cache: 'forever'}) + get selfEmitter(): this['SelfEmitter'] & EventEmitterWrapper { + const emitter = this.async.wrapEventEmitter({ on: (event: string, handler: Function, opts?: ComponentEmitterOptions) => - o.$on(normalizeEventName(event), handler, {...opts, rawEmitter: true}), + this.$on(normalizeEventName(event), handler, {...opts, rawEmitter: true}), once: (event: string, handler: Function, opts?: ComponentEmitterOptions) => - o.$once(normalizeEventName(event), handler, {...opts, rawEmitter: true}), + this.$once(normalizeEventName(event), handler, {...opts, rawEmitter: true}), off: (eventOrLink: string | EventId, handler: Function) => { if (Object.isString(eventOrLink)) { - return o.$off(normalizeEventName(eventOrLink), handler); + return this.$off(normalizeEventName(eventOrLink), handler); } - return o.$off(eventOrLink); + return this.$off(eventOrLink); }, - emit: o.emit.bind(o), - strictEmit: o.emit.bind(o) - }) - }) + emit: this.emit.bind(this), + strictEmit: this.emit.bind(this) + }); - // @ts-ignore (ts) - readonly selfEmitter!: this['SelfEmitter'] & EventEmitterWrapper; + return Object.cast(emitter); + } /** * The component local event emitter. @@ -130,23 +128,17 @@ export default abstract class iBlockEvent extends iBlockBase { * this.localEmitter.off({group: 'example.*'}); * ``` */ - @system({ - atom: true, - unique: true, - init: (_, d) => { - const emitter = (d.async).wrapEventEmitter(new EventEmitter({ - maxListeners: 10e3, - newListener: false, - wildcard: true - }), {group: ':suspend'}); - - emitter['strictEmit'] = (event: string, ...args: unknown[]) => emitter.emit(event, ...args); - return emitter; - } - }) - - // @ts-ignore (ts) - protected readonly localEmitter!: this['LocalEmitter'] & EventEmitterWrapper; + @computed({cache: 'forever'}) + protected get localEmitter(): this['LocalEmitter'] & EventEmitterWrapper { + const emitter = this.async.wrapEventEmitter(new EventEmitter({ + maxListeners: 10e3, + newListener: false, + wildcard: true + }), {group: ':suspend'}); + + emitter['strictEmit'] = (event: string, ...args: unknown[]) => emitter.emit(event, ...args); + return emitter; + } /** * The parent component event emitter. @@ -163,28 +155,25 @@ export default abstract class iBlockEvent extends iBlockBase { * this.parentEmitter.off({group: 'myEvent'}); * ``` */ - @system({ - atom: true, - unique: true, - init: (o, d) => (d.async).wrapEventEmitter({ + @computed({cache: 'forever'}) + protected get parentEmitter(): ReadonlyEventEmitterWrapper { + return this.async.wrapEventEmitter({ get on() { - const ee = o.$parent?.unsafe.selfEmitter; + const ee = this.$parent?.unsafe.selfEmitter; return ee?.on.bind(ee) ?? (() => Object.throw()); }, get once() { - const ee = o.$parent?.unsafe.selfEmitter; + const ee = this.$parent?.unsafe.selfEmitter; return ee?.once.bind(ee) ?? (() => Object.throw()); }, get off() { - const ee = o.$parent?.unsafe.selfEmitter; + const ee = this.$parent?.unsafe.selfEmitter; return ee?.off.bind(ee) ?? (() => Object.throw()); } - }) - }) - - protected readonly parentEmitter!: ReadonlyEventEmitterWrapper; + }); + } /** * The root component event emitter. @@ -202,14 +191,10 @@ export default abstract class iBlockEvent extends iBlockBase { * this.parentEmitter.off({group: 'myEvent'}); * ``` */ - @system({ - atom: true, - unique: true, - init: (o, d) => (d.async).wrapEventEmitter(o.r.unsafe.selfEmitter) - }) - - // @ts-ignore (ts) - protected readonly rootEmitter!: this['Root']['SelfEmitter'] & ReadonlyEventEmitterWrapper; + @computed({cache: 'forever'}) + protected get rootEmitter(): this['Root']['SelfEmitter'] & ReadonlyEventEmitterWrapper { + return this.async.wrapEventEmitter(this.r.unsafe.selfEmitter); + } /** * The global event emitter located in `core/component/event`. @@ -236,17 +221,12 @@ export default abstract class iBlockEvent extends iBlockBase { * this.globalEmitter.off({group: 'myEvent'}); * ``` */ - @system({ - atom: true, - unique: true, - init: (_, d) => { - const emitter = (d.async).wrapEventEmitter(globalEmitter); - emitter['strictEmit'] = (event: string, ...args: unknown[]) => emitter.emit(event, ...args); - return emitter; - } - }) - - protected readonly globalEmitter!: EventEmitterWrapper; + @computed({cache: 'forever'}) + protected get globalEmitter(): EventEmitterWrapper { + const emitter = this.async.wrapEventEmitter(globalEmitter); + emitter['strictEmit'] = (event: string, ...args: unknown[]) => emitter.emit(event, ...args); + return emitter; + } /** * Attaches an event listener to the specified component event @@ -442,11 +422,9 @@ export default abstract class iBlockEvent extends iBlockBase { */ dispatch: typeof this['selfEmitter']['emit'] = function dispatch(this: iBlockEvent, event: string | ComponentEvent, ...args: unknown[]): void { - const - that = this; + const that = this; - const - eventDecl = normalizeEvent(event); + const eventDecl = normalizeEvent(event); const eventName = eventDecl.event, @@ -458,8 +436,7 @@ export default abstract class iBlockEvent extends iBlockBase { $parent: parent } = this; - const - logArgs = args.slice(); + const logArgs = args.slice(); if (eventDecl.logLevel === 'error') { logArgs.forEach((el, i) => { @@ -550,8 +527,7 @@ export default abstract class iBlockEvent extends iBlockBase { */ protected waitRef>(ref: string, opts?: AsyncOptions): Promise { return this.async.promise(() => new SyncPromise((resolve) => { - const - refVal = this.$refs[ref]; + const refVal = this.$refs[ref]; if (refVal != null && (!Object.isArray(refVal) || refVal.length > 0)) { resolve(refVal); @@ -574,8 +550,7 @@ export default abstract class iBlockEvent extends iBlockBase { protected override initBaseAPI(): void { super.initBaseAPI(); - const - i = this.instance; + const i = this.instance; this.on = i.on.bind(this); this.once = i.once.bind(this); From 56366050ae40f91b2eb9f3f53657332dd8cdc00f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 13:57:55 +0300 Subject: [PATCH 028/334] chore: removed dead code --- src/core/component/gc/helpers.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/core/component/gc/helpers.ts b/src/core/component/gc/helpers.ts index ddfb1559c5..dca0005ac3 100644 --- a/src/core/component/gc/helpers.ts +++ b/src/core/component/gc/helpers.ts @@ -20,18 +20,6 @@ export function delay(): Promise { return; } - if (typeof requestIdleCallback === 'function') { - requestIdleCallback(() => { - resolve(); - }, {timeout: config.gc.delay}); - - } else { - setTimeout(() => { - resolve(); - - // To avoid freezing during cleaning of a larger number of components at once, - // a little randomness is added to the process - }, config.gc.delay - Math.floor(Math.random() * (config.gc.delay / 3))); - } + requestIdleCallback(() => resolve(), {timeout: config.gc.delay}); })); } From 1638c7abbf184f68fcf22c3b274e59f8a1d53dbd Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 15:01:38 +0300 Subject: [PATCH 029/334] fix: fixed an error after refactoring --- src/components/super/i-block/base/index.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index a6af56eb2e..ceceb179ce 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -57,7 +57,8 @@ import type { AsyncWatchOptions } from 'components/friends/sync'; import iBlockFriends from 'components/super/i-block/friends'; const - $$ = symbolGenerator(); + $$ = symbolGenerator(), + i18nKeysets = new Map(); @component({partial: 'iBlock'}) export default abstract class iBlockBase extends iBlockFriends { @@ -227,16 +228,23 @@ export default abstract class iBlockBase extends iBlockFriends { */ @computed({cache: 'forever'}) protected get componentI18nKeysets(): string[] { - const res: string[] = []; + const {constructor} = this.meta; - let keyset: CanUndef = getComponentName(this.constructor); + let keysets: CanUndef = i18nKeysets.get(constructor); - while (keyset != null) { - res.push(keyset); - keyset = config.components[keyset]?.parent; + if (keysets == null) { + keysets = []; + i18nKeysets.set(constructor, keysets); + + let keyset: CanUndef = getComponentName(constructor); + + while (keyset != null) { + keysets.push(keyset); + keyset = config.components[keyset]?.parent; + } } - return res; + return keysets; } /** From affa983ffb2721cda571397264e628daf1c6f656 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 15:31:55 +0300 Subject: [PATCH 030/334] fix: fixed an error after refactoring --- src/components/super/i-block/event/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index ef91d53678..5b1fc42022 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -157,19 +157,21 @@ export default abstract class iBlockEvent extends iBlockBase { */ @computed({cache: 'forever'}) protected get parentEmitter(): ReadonlyEventEmitterWrapper { + const that = this; + return this.async.wrapEventEmitter({ get on() { - const ee = this.$parent?.unsafe.selfEmitter; + const ee = that.$parent?.selfEmitter; return ee?.on.bind(ee) ?? (() => Object.throw()); }, get once() { - const ee = this.$parent?.unsafe.selfEmitter; + const ee = that.$parent?.selfEmitter; return ee?.once.bind(ee) ?? (() => Object.throw()); }, get off() { - const ee = this.$parent?.unsafe.selfEmitter; + const ee = that.$parent?.selfEmitter; return ee?.off.bind(ee) ?? (() => Object.throw()); } }); From a5539e51454475e8eddb570e78f001ab80032ca6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 17:10:57 +0300 Subject: [PATCH 031/334] chore!: new unsafe interface & fixed TS errors & refactoring --- .../base/b-cache-ssr/b-cache-ssr.ts | 2 + src/components/base/b-list/modules/values.ts | 5 +- src/components/base/b-router/b-router.ts | 7 ++- src/components/base/b-router/interface.ts | 4 +- src/components/base/b-slider/b-slider.ts | 3 +- src/components/base/b-tree/b-tree.ts | 6 +- src/components/base/b-tree/modules/values.ts | 5 +- .../b-virtual-scroll-new.ts | 6 +- .../modules/factory/index.ts | 3 +- .../observer/engines/intersection-observer.ts | 7 +-- .../modules/observer/index.ts | 3 +- .../modules/slots/index.ts | 4 +- .../modules/state/index.ts | 3 +- .../base/b-virtual-scroll-new/props.ts | 3 +- .../b-virtual-scroll/modules/chunk-render.ts | 3 +- .../b-virtual-scroll/modules/chunk-request.ts | 3 +- .../modules/component-render.ts | 3 +- src/components/base/b-window/b-window.ts | 3 +- src/components/dummies/b-dummy/b-dummy.ts | 3 +- src/components/form/b-button/b-button.ts | 3 +- src/components/form/b-checkbox/b-checkbox.ts | 13 ++-- .../form/b-hidden-input/b-hidden-input.ts | 3 +- src/components/form/b-input/b-input.ts | 14 ++++- .../form/b-select-date/b-select-date.ts | 12 +++- src/components/form/b-select/b-select.ts | 6 +- .../b-select/modules/select-event-handlers.ts | 4 +- .../form/b-select/modules/values.ts | 3 +- src/components/form/b-select/props.ts | 9 ++- src/components/form/b-textarea/b-textarea.ts | 13 +++- src/components/friends/async-render/render.ts | 2 +- src/components/friends/daemons/class.ts | 5 -- .../b-friends-daemons-dummy.ts | 9 +-- src/components/friends/data-provider/class.ts | 7 ++- src/components/friends/friend/class.ts | 8 +-- src/components/friends/state/class.ts | 2 +- src/components/friends/sync/link.ts | 2 +- src/components/friends/sync/object.ts | 2 +- src/components/friends/vdom/class.ts | 6 +- src/components/friends/vdom/render.ts | 4 +- .../super/i-block/decorators/interface.ts | 2 +- .../super/i-block/decorators/wait.ts | 8 ++- src/components/super/i-block/i-block.ts | 6 +- src/components/super/i-block/interface.ts | 27 +++------ .../super/i-block/modules/mods/index.ts | 25 ++++---- src/components/super/i-block/props.ts | 5 +- .../super/i-block/providers/index.ts | 30 ++++------ src/components/super/i-block/state/index.ts | 60 +++++++++---------- .../b-super-i-block-deactivation-dummy.ts | 3 +- .../b-super-i-block-mods-dummy.ts | 3 +- .../b-super-i-block-teleport-dummy.ts | 3 +- .../b-super-i-block-watch-dummy.ts | 2 +- src/components/super/i-data/README.md | 6 +- src/components/super/i-data/i-data.ts | 3 +- src/components/super/i-data/interface.ts | 1 - .../super/i-input-text/i-input-text.ts | 6 +- .../super/i-input-text/mask/class.ts | 3 +- src/components/super/i-input/README.md | 10 ++-- src/components/super/i-input/fields.ts | 3 +- src/components/super/i-input/i-input.ts | 3 +- src/components/super/i-page/README.md | 10 ++-- src/components/super/i-page/i-page.ts | 2 +- src/components/super/i-page/interface.ts | 10 ++-- .../super/i-static-page/i-static-page.ts | 7 ++- .../traits/i-active-items/i-active-items.ts | 4 +- .../traits/i-history/history/index.ts | 3 +- src/core/component/const/cache.ts | 1 + src/core/component/context/index.ts | 40 ++++++++++++- src/core/component/decorators/factory.ts | 2 +- .../b-effect-prop-wrapper-dummy.ts | 3 +- src/core/component/directives/helpers.ts | 5 +- .../b-directives-ref-dummy.ts | 3 +- src/core/component/engines/vue3/component.ts | 25 +++----- src/core/component/field/helpers.ts | 9 +-- src/core/component/field/init.ts | 10 +--- src/core/component/functional/context.ts | 8 ++- src/core/component/functional/helpers.ts | 8 +-- .../b-functional-dummy/b-functional-dummy.ts | 3 +- .../b-functional-getters-dummy.ts | 3 +- src/core/component/hook/index.ts | 2 +- src/core/component/init/README.md | 5 +- src/core/component/init/component.ts | 4 +- src/core/component/init/states/activated.ts | 3 +- .../component/init/states/before-create.ts | 15 +++-- .../init/states/before-data-create.ts | 1 - .../component/init/states/before-destroy.ts | 3 +- .../component/init/states/before-mount.ts | 6 +- .../component/init/states/before-update.ts | 3 +- src/core/component/init/states/created.ts | 8 +-- src/core/component/init/states/deactivated.ts | 3 +- src/core/component/init/states/destroyed.ts | 3 +- .../component/init/states/error-captured.ts | 3 +- src/core/component/init/states/mounted.ts | 3 +- .../component/init/states/render-trigered.ts | 3 +- src/core/component/init/states/updated.ts | 3 +- .../interface/component/component.ts | 9 +-- .../component/interface/component/unsafe.ts | 25 +++----- src/core/component/interface/index.ts | 2 + src/core/component/interface/link.ts | 2 +- src/core/component/interface/mod.ts | 6 +- src/core/component/meta/create.ts | 11 ++-- src/core/component/meta/interface/index.ts | 34 +++++------ src/core/component/queue-emitter/index.ts | 9 +-- src/core/component/reflect/interface.ts | 5 +- src/core/component/reflect/property.ts | 3 +- src/core/component/watch/bind.ts | 2 +- .../page-meta-data/elements/link/index.ts | 7 ++- .../page-meta-data/elements/meta/index.ts | 7 ++- .../page-meta-data/elements/title/index.ts | 7 ++- 108 files changed, 417 insertions(+), 348 deletions(-) diff --git a/src/components/base/b-cache-ssr/b-cache-ssr.ts b/src/components/base/b-cache-ssr/b-cache-ssr.ts index c5944d5a4c..ae79ee2413 100644 --- a/src/components/base/b-cache-ssr/b-cache-ssr.ts +++ b/src/components/base/b-cache-ssr/b-cache-ssr.ts @@ -23,6 +23,7 @@ export * from 'components/base/b-cache-ssr/const'; @component({functional: true}) export default class bCacheSSR extends iBlock { @prop({required: true}) + // @ts-ignore (override) override readonly globalName!: string; override readonly rootTag: string = 'div'; @@ -35,6 +36,7 @@ export default class bCacheSSR extends iBlock { } @system(() => ssrCache) + // @ts-ignore (override) protected override readonly $ssrCache!: AbstractCache; protected override get state(): SuperState { diff --git a/src/components/base/b-list/modules/values.ts b/src/components/base/b-list/modules/values.ts index 8286b3e120..a4a04b73f9 100644 --- a/src/components/base/b-list/modules/values.ts +++ b/src/components/base/b-list/modules/values.ts @@ -13,7 +13,8 @@ import type bList from 'components/base/b-list/b-list'; import type { Item } from 'components/base/b-list/b-list'; export default class Values extends Friend { - override readonly C!: bList; + /** @inheritDoc */ + declare readonly C: bList; /** * A map of the item indexes and their values @@ -104,7 +105,7 @@ export default class Values extends Friend { } if (activeItem != null) { - iActiveItems.initItem(ctx, activeItem); + iActiveItems.initItem(this.component, activeItem); } } } diff --git a/src/components/base/b-router/b-router.ts b/src/components/base/b-router/b-router.ts index c38be5ac31..83dbd2d8f0 100644 --- a/src/components/base/b-router/b-router.ts +++ b/src/components/base/b-router/b-router.ts @@ -52,7 +52,8 @@ const @component() export default class bRouter extends iRouterProps { - public override async!: Async; + /** @inheritDoc */ + declare public async: Async; /** * Compiled application route map @@ -60,7 +61,6 @@ export default class bRouter extends iRouterProps { */ @system({ after: 'engine', - // eslint-disable-next-line @v4fire/unbound-method init: (o) => o.sync.link(o.compileStaticRoutes) }) @@ -92,7 +92,8 @@ export default class bRouter extends iRouterProps { @system() private previousTransitionOptions: Nullable; - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/base/b-router/interface.ts b/src/components/base/b-router/interface.ts index 00402742b7..5db6fcc402 100644 --- a/src/components/base/b-router/interface.ts +++ b/src/components/base/b-router/interface.ts @@ -49,7 +49,6 @@ export type TransitionMethod = 'push' | 'replace' | 'event'; export type ComputeParamFn = (ctx: bRouter) => unknown; export type RouteOption = Dictionary; -// @ts-ignore (extend) export interface UnsafeBRouter extends UnsafeIData { // @ts-ignore (access) engine: CTX['engine']; @@ -59,4 +58,7 @@ export interface UnsafeBRouter extends UnsafeIDat // @ts-ignore (access) initRoute: CTX['initRoute']; + + // @ts-ignore (access) + compileStaticRoutes: CTX['compileStaticRoutes']; } diff --git a/src/components/base/b-slider/b-slider.ts b/src/components/base/b-slider/b-slider.ts index b479344c45..ddf7d8cf45 100644 --- a/src/components/base/b-slider/b-slider.ts +++ b/src/components/base/b-slider/b-slider.ts @@ -172,7 +172,8 @@ class bSlider extends iSliderProps implements iObserveDOM, iItems { @field((o) => o.sync.link()) protected mode!: Mode; - protected override readonly $refs!: iSliderProps['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iSliderProps['$refs'] & { view?: HTMLElement; content?: HTMLElement; contentWrapper?: HTMLElement; diff --git a/src/components/base/b-tree/b-tree.ts b/src/components/base/b-tree/b-tree.ts index 8daccee619..49abc8c6fe 100644 --- a/src/components/base/b-tree/b-tree.ts +++ b/src/components/base/b-tree/b-tree.ts @@ -53,7 +53,8 @@ interface bTree extends Trait, Trait {} @derive(iActiveItems, iFoldable) class bTree extends iTreeProps implements iActiveItems, iFoldable { - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } @@ -138,7 +139,8 @@ class bTree extends iTreeProps implements iActiveItems, iFoldable { protected itemsStore: this['Items'] = []; - protected override readonly $refs!: iData['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iData['$refs'] & { children?: bTree[]; }; diff --git a/src/components/base/b-tree/modules/values.ts b/src/components/base/b-tree/modules/values.ts index 4b04629b3e..23b8d26813 100644 --- a/src/components/base/b-tree/modules/values.ts +++ b/src/components/base/b-tree/modules/values.ts @@ -13,7 +13,8 @@ import type bTree from 'components/base/b-tree/b-tree'; import type { Item } from 'components/base/b-tree/b-tree'; export default class Values extends Friend { - override readonly C!: bTree; + /** @inheritDoc */ + declare readonly C: bTree; /** * A map of the item indexes and their values @@ -95,7 +96,7 @@ export default class Values extends Friend { } if (activeItem != null) { - iActiveItems.initItem(ctx, activeItem); + iActiveItems.initItem(this.component, activeItem); } } diff --git a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts index 736af94e3e..ebde54f176 100644 --- a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts +++ b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts @@ -83,7 +83,8 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements @system((ctx) => new Observer(ctx)) protected readonly observer!: Observer; - protected override readonly $refs!: iData['$refs'] & $ComponentRefs; + /** @inheritDoc */ + declare protected readonly $refs: iData['$refs'] & $ComponentRefs; // @ts-ignore (getter instead readonly) override get requestParams(): iData['requestParams'] { @@ -95,7 +96,8 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements }; } - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/base/b-virtual-scroll-new/modules/factory/index.ts b/src/components/base/b-virtual-scroll-new/modules/factory/index.ts index b3ae051315..fe5a5bb541 100644 --- a/src/components/base/b-virtual-scroll-new/modules/factory/index.ts +++ b/src/components/base/b-virtual-scroll-new/modules/factory/index.ts @@ -22,7 +22,8 @@ import * as vdomRender from 'components/base/b-virtual-scroll-new/modules/factor * specifically tailored for the `bVirtualScrollNew` class. */ export class ComponentFactory extends Friend { - override readonly C!: bVirtualScrollNew; + /** @inheritDoc */ + declare readonly C: bVirtualScrollNew; /** * Produces component items based on the current state and context. diff --git a/src/components/base/b-virtual-scroll-new/modules/observer/engines/intersection-observer.ts b/src/components/base/b-virtual-scroll-new/modules/observer/engines/intersection-observer.ts index 1e67e819ad..717efca506 100644 --- a/src/components/base/b-virtual-scroll-new/modules/observer/engines/intersection-observer.ts +++ b/src/components/base/b-virtual-scroll-new/modules/observer/engines/intersection-observer.ts @@ -15,11 +15,8 @@ import { observerAsyncGroup } from 'components/base/b-virtual-scroll-new/modules import type { ObserverEngine } from 'components/base/b-virtual-scroll-new/modules/observer/interface'; export default class IoObserver extends Friend implements ObserverEngine { - - /** - * {@link bVirtualScrollNew} - */ - override readonly C!: bVirtualScrollNew; + /** @inheritDoc */ + declare readonly C: bVirtualScrollNew; /** * {@link ObserverEngine.watchForIntersection} diff --git a/src/components/base/b-virtual-scroll-new/modules/observer/index.ts b/src/components/base/b-virtual-scroll-new/modules/observer/index.ts index 2f7eeba10f..590f3114dd 100644 --- a/src/components/base/b-virtual-scroll-new/modules/observer/index.ts +++ b/src/components/base/b-virtual-scroll-new/modules/observer/index.ts @@ -20,7 +20,8 @@ export { default as IoObserver } from 'components/base/b-virtual-scroll-new/modu * It provides observation capabilities using different engines such as IoObserver and ScrollObserver. */ export class Observer extends Friend { - override readonly C!: bVirtualScrollNew; + /** @inheritDoc */ + declare readonly C: bVirtualScrollNew; /** * The observation engine used by the Observer. diff --git a/src/components/base/b-virtual-scroll-new/modules/slots/index.ts b/src/components/base/b-virtual-scroll-new/modules/slots/index.ts index 40898dc80d..748db1f0c7 100644 --- a/src/components/base/b-virtual-scroll-new/modules/slots/index.ts +++ b/src/components/base/b-virtual-scroll-new/modules/slots/index.ts @@ -24,8 +24,8 @@ export const * A class that manages the visibility of slots based on different states. */ export class SlotsStateController extends Friend { - - override readonly C!: bVirtualScrollNew; + /** @inheritDoc */ + declare readonly C: bVirtualScrollNew; /** * Options for the asynchronous operations. diff --git a/src/components/base/b-virtual-scroll-new/modules/state/index.ts b/src/components/base/b-virtual-scroll-new/modules/state/index.ts index d12b890993..5254ebfe06 100644 --- a/src/components/base/b-virtual-scroll-new/modules/state/index.ts +++ b/src/components/base/b-virtual-scroll-new/modules/state/index.ts @@ -17,7 +17,8 @@ import type { MountedChild, VirtualScrollState, MountedItem, PrivateComponentSta * Friendly to the `bVirtualScrollNew` class that represents the internal state of a component. */ export class ComponentInternalState extends Friend { - override readonly C!: bVirtualScrollNew; + /** @inheritDoc */ + declare readonly C: bVirtualScrollNew; /** * Current state of the component. diff --git a/src/components/base/b-virtual-scroll-new/props.ts b/src/components/base/b-virtual-scroll-new/props.ts index a7b974c89c..f13070dff5 100644 --- a/src/components/base/b-virtual-scroll-new/props.ts +++ b/src/components/base/b-virtual-scroll-new/props.ts @@ -243,7 +243,8 @@ export default abstract class iVirtualScrollProps extends iData { readonly itemsProcessors?: ItemsProcessors; - override readonly DB!: ComponentDb; + /** @inheritDoc */ + declare readonly DB: ComponentDb; /** * A function that returns the GET parameters for a request. This function is called for each request. It receives the diff --git a/src/components/base/b-virtual-scroll/modules/chunk-render.ts b/src/components/base/b-virtual-scroll/modules/chunk-render.ts index 934697009a..cde89a216b 100644 --- a/src/components/base/b-virtual-scroll/modules/chunk-render.ts +++ b/src/components/base/b-virtual-scroll/modules/chunk-render.ts @@ -27,7 +27,8 @@ const $$ = symbolGenerator(); export default class ChunkRender extends Friend { - override readonly C!: bVirtualScroll; + /** @inheritDoc */ + declare readonly C: bVirtualScroll; /** * Render items diff --git a/src/components/base/b-virtual-scroll/modules/chunk-request.ts b/src/components/base/b-virtual-scroll/modules/chunk-request.ts index 49d332b696..631d016932 100644 --- a/src/components/base/b-virtual-scroll/modules/chunk-request.ts +++ b/src/components/base/b-virtual-scroll/modules/chunk-request.ts @@ -19,7 +19,8 @@ const $$ = symbolGenerator(); export default class ChunkRequest extends Friend { - override readonly C!: bVirtualScroll; + /** @inheritDoc */ + declare readonly C: bVirtualScroll; /** * Current page diff --git a/src/components/base/b-virtual-scroll/modules/component-render.ts b/src/components/base/b-virtual-scroll/modules/component-render.ts index 38b7d8c43f..eed5ad19a7 100644 --- a/src/components/base/b-virtual-scroll/modules/component-render.ts +++ b/src/components/base/b-virtual-scroll/modules/component-render.ts @@ -15,7 +15,8 @@ import type bVirtualScroll from 'components/base/b-virtual-scroll/b-virtual-scro import type { RenderItem, DataToRender, ItemAttrs, VirtualItemEl } from 'components/base/b-virtual-scroll/interface'; export default class ComponentRender extends Friend { - override readonly C!: bVirtualScroll; + /** @inheritDoc */ + declare readonly C: bVirtualScroll; /** * Async group diff --git a/src/components/base/b-window/b-window.ts b/src/components/base/b-window/b-window.ts index b4c16290bc..6c156f15e7 100644 --- a/src/components/base/b-window/b-window.ts +++ b/src/components/base/b-window/b-window.ts @@ -143,7 +143,8 @@ class bWindow extends iData implements iVisible, iWidth, iOpenToggle, iLockPageS ] }; - protected override readonly $refs!: iData['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iData['$refs'] & { window: HTMLElement; }; diff --git a/src/components/dummies/b-dummy/b-dummy.ts b/src/components/dummies/b-dummy/b-dummy.ts index 7048cccc46..d2e3948633 100644 --- a/src/components/dummies/b-dummy/b-dummy.ts +++ b/src/components/dummies/b-dummy/b-dummy.ts @@ -56,7 +56,8 @@ export default class bDummy extends iData { @field() storedAccessors: Dictionary = {}; - protected override readonly $refs!: iData['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iData['$refs'] & { testComponent?: iBlock; }; diff --git a/src/components/form/b-button/b-button.ts b/src/components/form/b-button/b-button.ts index 73509f731e..3773f437af 100644 --- a/src/components/form/b-button/b-button.ts +++ b/src/components/form/b-button/b-button.ts @@ -129,7 +129,8 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi ] }; - protected override readonly $refs!: iButtonProps['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iButtonProps['$refs'] & { button: HTMLButtonElement; file?: HTMLInputElement; dropdown?: Element; diff --git a/src/components/form/b-checkbox/b-checkbox.ts b/src/components/form/b-checkbox/b-checkbox.ts index 01b2270fe9..95f3f300c0 100644 --- a/src/components/form/b-checkbox/b-checkbox.ts +++ b/src/components/form/b-checkbox/b-checkbox.ts @@ -48,8 +48,11 @@ export { Value, FormValue }; }) export default class bCheckbox extends iInput implements iSize { - override readonly Value!: Value; - override readonly FormValue!: FormValue; + /** @inheritDoc */ + declare readonly Value: Value; + + /** @inheritDoc */ + declare readonly FormValue: FormValue; /** * If true, the component is checked by default. @@ -181,9 +184,11 @@ export default class bCheckbox extends iInput implements iSize { }; @system() - protected override valueStore!: this['Value']; + // @ts-ignore (override) + protected override valueStore: this['Value']; - protected override readonly $refs!: iInput['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iInput['$refs'] & { input: HTMLInputElement; }; diff --git a/src/components/form/b-hidden-input/b-hidden-input.ts b/src/components/form/b-hidden-input/b-hidden-input.ts index 2bc69df9c2..a1a594e130 100644 --- a/src/components/form/b-hidden-input/b-hidden-input.ts +++ b/src/components/form/b-hidden-input/b-hidden-input.ts @@ -23,7 +23,8 @@ export * from 'components/super/i-input/i-input'; }) export default class bHiddenInput extends iInput { - protected override readonly $refs!: iInput['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iInput['$refs'] & { input: HTMLInputElement; }; } diff --git a/src/components/form/b-input/b-input.ts b/src/components/form/b-input/b-input.ts index b995e83754..784f9818f1 100644 --- a/src/components/form/b-input/b-input.ts +++ b/src/components/form/b-input/b-input.ts @@ -51,13 +51,18 @@ const }) export default class bInput extends iInputText { - override readonly Value!: Value; - override readonly FormValue!: FormValue; + /** @inheritDoc */ + declare readonly Value: Value; + + /** @inheritDoc */ + declare readonly FormValue: FormValue; @prop({type: String, required: false}) + // @ts-ignore (override) override readonly valueProp?: this['Value']; @prop({type: String, required: false}) + // @ts-ignore (override) override readonly defaultProp?: this['Value']; /** @@ -219,11 +224,13 @@ export default class bInput extends iInputText { ...Validators }; - protected override readonly $refs!: iInputText['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iInputText['$refs'] & { textHint?: HTMLSpanElement; }; @system() + // @ts-ignore (override) protected override valueStore!: this['Value']; /** @@ -270,6 +277,7 @@ export default class bInput extends iInputText { }) }) + // @ts-ignore (override) protected override textStore!: string; @wait('ready', {label: $$.clear}) diff --git a/src/components/form/b-select-date/b-select-date.ts b/src/components/form/b-select-date/b-select-date.ts index 7993fd92c3..16adddff86 100644 --- a/src/components/form/b-select-date/b-select-date.ts +++ b/src/components/form/b-select-date/b-select-date.ts @@ -34,13 +34,18 @@ export { Value, FormValue }; }) export default class bSelectDate extends iInput implements iWidth { - override readonly Value!: Value; - override readonly FormValue!: FormValue; + /** @inheritDoc */ + declare readonly Value: Value; + + /** @inheritDoc */ + declare readonly FormValue: FormValue; @prop({type: Date, required: false}) + // @ts-ignore (override) override readonly valueProp?: this['Value']; @prop({type: Date, required: false}) + // @ts-ignore (override) override readonly defaultProp?: this['Value']; /** @@ -65,7 +70,8 @@ export default class bSelectDate extends iInput implements iWidth { ...iWidth.mods }; - protected override readonly $refs!: iInput['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iInput['$refs'] & { input: HTMLInputElement; month: bSelect; day: bSelect; diff --git a/src/components/form/b-select/b-select.ts b/src/components/form/b-select/b-select.ts index 28562224fd..aeb64e825a 100644 --- a/src/components/form/b-select/b-select.ts +++ b/src/components/form/b-select/b-select.ts @@ -75,7 +75,8 @@ interface bSelect extends Trait, Trait, @derive(SelectEventHandlers, iOpenToggle, iActiveItems) class bSelect extends iSelectProps implements iOpenToggle, iActiveItems { - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } @@ -249,7 +250,8 @@ class bSelect extends iSelectProps implements iOpenToggle, iActiveItems { @system() protected keydownHandlerEnabled: boolean = false; - protected override readonly $refs!: iInputText['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iInputText['$refs'] & { dropdown?: Element; }; diff --git a/src/components/form/b-select/modules/select-event-handlers.ts b/src/components/form/b-select/modules/select-event-handlers.ts index 66b5d0db54..a956b257d5 100644 --- a/src/components/form/b-select/modules/select-event-handlers.ts +++ b/src/components/form/b-select/modules/select-event-handlers.ts @@ -32,7 +32,7 @@ export default abstract class SelectEventHandlers { // Status: opened == false or opened == null if (e.type === 'set' && e.value === 'false' || e.type === 'remove') { - if (openedSelect.link === unsafe) { + if (openedSelect.link === component) { openedSelect.link = null; } @@ -50,7 +50,7 @@ export default abstract class SelectEventHandlers { openedSelect.link.close().catch(() => undefined); } - openedSelect.link = unsafe; + openedSelect.link = component; } component.handleKeydown(true); diff --git a/src/components/form/b-select/modules/values.ts b/src/components/form/b-select/modules/values.ts index 141cf39be1..02a5180372 100644 --- a/src/components/form/b-select/modules/values.ts +++ b/src/components/form/b-select/modules/values.ts @@ -12,7 +12,8 @@ import type bSelect from 'components/form/b-select/b-select'; import type { Item } from 'components/form/b-select/interface'; export default class Values extends Friend { - override readonly C!: bSelect; + /** @inheritDoc */ + declare readonly C: bSelect; /** * A map of the item indexes and their values diff --git a/src/components/form/b-select/props.ts b/src/components/form/b-select/props.ts index 6d134c9c14..3efe0ca675 100644 --- a/src/components/form/b-select/props.ts +++ b/src/components/form/b-select/props.ts @@ -15,9 +15,11 @@ import type { Value, FormValue, Item } from 'components/form/b-select/interface' @component({partial: 'bSelect'}) export default abstract class iSelectProps extends iInputText { - override readonly Value!: Value; + /** @inheritDoc */ + declare readonly Value: Value; - override readonly FormValue!: FormValue; + /** @inheritDoc */ + declare readonly FormValue: FormValue; /** {@link iItems.Item} */ readonly Item!: Item; @@ -31,7 +33,8 @@ export default abstract class iSelectProps extends iInputText { /** {@link iActiveItems.Active} */ readonly Active!: iActiveItems['Active']; - override readonly valueProp?: this['ActiveProp']; + /** @inheritDoc */ + declare readonly valueProp?: this['ActiveProp']; /** * @alias valueProp diff --git a/src/components/form/b-textarea/b-textarea.ts b/src/components/form/b-textarea/b-textarea.ts index 52cce9557f..6587e44dc3 100644 --- a/src/components/form/b-textarea/b-textarea.ts +++ b/src/components/form/b-textarea/b-textarea.ts @@ -54,13 +54,18 @@ const }) export default class bTextarea extends iInputText { - override readonly Value!: Value; - override readonly FormValue!: FormValue; + /** @inheritDoc */ + declare readonly Value: Value; + + /** @inheritDoc */ + declare readonly FormValue: FormValue; @prop({type: String, required: false}) + // @ts-ignore (override) override readonly valueProp?: this['Value']; @prop({type: String, required: false}) + // @ts-ignore (override) override readonly defaultProp?: this['Value']; /** @@ -136,6 +141,7 @@ export default class bTextarea extends iInputText { }; @system() + // @ts-ignore (override) protected override valueStore!: this['Value']; @system({ @@ -182,7 +188,8 @@ export default class bTextarea extends iInputText { @system() protected minHeight: number = 0; - protected override readonly $refs!: iInputText['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iInputText['$refs'] & { input: HTMLTextAreaElement; }; diff --git a/src/components/friends/async-render/render.ts b/src/components/friends/async-render/render.ts index 64015ec685..f5dcad36d8 100644 --- a/src/components/friends/async-render/render.ts +++ b/src/components/friends/async-render/render.ts @@ -70,7 +70,7 @@ export function waitForceRender( el: Nullable; if (Object.isFunction(elementToDrop)) { - el = await elementToDrop(this.ctx); + el = await elementToDrop(this.component); } else { el = elementToDrop; diff --git a/src/components/friends/daemons/class.ts b/src/components/friends/daemons/class.ts index c92b3a4cfd..71bf10540c 100644 --- a/src/components/friends/daemons/class.ts +++ b/src/components/friends/daemons/class.ts @@ -24,11 +24,6 @@ class Daemons extends Friend { return Object.cast((this.ctx.instance.constructor).daemons); } - constructor(component: iBlock) { - super(component); - this.init(); - } - init(): void { if (Object.size(this.daemons) === 0) { return; diff --git a/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts b/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts index aaff3908d9..ea29db3339 100644 --- a/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts +++ b/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts @@ -17,11 +17,10 @@ export * from 'components/dummies/b-dummy/b-dummy'; Daemons.addToPrototype(DaemonsAPI); -interface bFriendsDaemonsDummy extends Dictionary {} - @component() -class bFriendsDaemonsDummy extends bDummy { - override get unsafe(): UnsafeGetter> { +export default class bFriendsDaemonsDummy extends bDummy { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } @@ -92,5 +91,3 @@ class bFriendsDaemonsDummy extends bDummy { } }; } - -export default bFriendsDaemonsDummy; diff --git a/src/components/friends/data-provider/class.ts b/src/components/friends/data-provider/class.ts index 4e050f2a0a..00c285d2e4 100644 --- a/src/components/friends/data-provider/class.ts +++ b/src/components/friends/data-provider/class.ts @@ -49,8 +49,11 @@ interface DataProvider { ) class DataProvider extends Friend { - override readonly C!: iBlock & iDataProvider; - override readonly CTX!: this['C']['unsafe'] & iDataProvider; + /** @inheritDoc */ + declare readonly C: iBlock & iDataProvider; + + /** @inheritDoc */ + declare readonly CTX: this['C']['unsafe'] & iDataProvider; /** * The component data provider event emitter. diff --git a/src/components/friends/friend/class.ts b/src/components/friends/friend/class.ts index df00c5759e..81eb08fba4 100644 --- a/src/components/friends/friend/class.ts +++ b/src/components/friends/friend/class.ts @@ -121,14 +121,14 @@ export default class Friend { return this.ctx.remoteState; } - constructor(component: iBlock) { - this.ctx = component.unsafe; - this.component = component; + constructor(component: iBlock | iBlock['unsafe']) { + this.ctx = Object.cast(component.unsafe); + this.component = Object.cast(component); this.ctx.$async.worker(() => { const that = this; - // We are cleaning memory in a deferred way, because this API may be needed when processing the destroyed hook + // We are cleaning memory in a deferred way, because this API may be necessary when processing the destroyed hook // eslint-disable-next-line require-yield gc.add(function* destructor() { // @ts-ignore (unsafe) diff --git a/src/components/friends/state/class.ts b/src/components/friends/state/class.ts index dcd1ed24ff..201088d8d1 100644 --- a/src/components/friends/state/class.ts +++ b/src/components/friends/state/class.ts @@ -43,7 +43,7 @@ class State extends Friend { get needRouterSync(): boolean { // @ts-ignore (access) baseSyncRouterState ??= iBlock.prototype.syncRouterState; - return baseSyncRouterState !== this.instance.syncRouterState; + return baseSyncRouterState !== Object.cast(this.instance).syncRouterState; } /** {@link iBlock.instance} */ diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 361680fd73..14ca4a7b6b 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -325,7 +325,7 @@ export function link( isCustomWatcher = true; } else { - info = getPropertyInfo(normalizedPath, this.ctx); + info = getPropertyInfo(normalizedPath, this.component); if (info.type === 'mounted') { isMountedWatcher = true; diff --git a/src/components/friends/sync/object.ts b/src/components/friends/sync/object.ts index 43f8b27777..e2a74f25db 100644 --- a/src/components/friends/sync/object.ts +++ b/src/components/friends/sync/object.ts @@ -307,7 +307,7 @@ export function object( isCustomWatcher = true; } else { - info = getPropertyInfo(watchPath, this.ctx); + info = getPropertyInfo(watchPath, this.component); if (info.type === 'mounted') { isMountedWatcher = true; diff --git a/src/components/friends/vdom/class.ts b/src/components/friends/vdom/class.ts index e21907f029..c9f7989c93 100644 --- a/src/components/friends/vdom/class.ts +++ b/src/components/friends/vdom/class.ts @@ -83,11 +83,9 @@ class VDOM extends Friend { * Saves the component active rendering context */ saveRenderContext(): void { - const - {ctx} = this; + const {ctx} = this; - const - withCtx = ctx.$renderEngine.r.withCtx((cb) => cb()); + const withCtx = ctx.$renderEngine.r.withCtx((cb) => cb()); ctx.$withCtx = (cb) => { if (ctx.hook === 'mounted' || ctx.hook === 'updated') { diff --git a/src/components/friends/vdom/render.ts b/src/components/friends/vdom/render.ts index d7d4fb4c2e..c810f706c5 100644 --- a/src/components/friends/vdom/render.ts +++ b/src/components/friends/vdom/render.ts @@ -48,7 +48,7 @@ export function render(this: Friend, vnode: VNode, group?: string): Node; export function render(this: Friend, vnodes: VNode[], group?: string): Node[]; export function render(this: Friend, vnode: CanArray, group?: string): CanArray { - return this.ctx.$renderEngine.r.render(Object.cast(vnode), this.ctx, group); + return this.ctx.$renderEngine.r.render(Object.cast(vnode), this.component, group); } /** @@ -127,7 +127,7 @@ export function getRenderFactory(this: Friend, path: string): CanUndef | string, - ctx: ComponentInterface = this.ctx + ctx: ComponentInterface = this.component ): RenderFn { const factory = Object.isString(factoryOrPath) ? getRenderFactory.call(this, factoryOrPath) : factoryOrPath; diff --git a/src/components/super/i-block/decorators/interface.ts b/src/components/super/i-block/decorators/interface.ts index 0ab73a55fd..047220ea6d 100644 --- a/src/components/super/i-block/decorators/interface.ts +++ b/src/components/super/i-block/decorators/interface.ts @@ -69,7 +69,7 @@ export type WaitStatuses = export interface WaitDecoratorOptions extends AsyncOptions { /** - * If true, then the wrapped function will always be executed deferred + * If set to true, the wrapped function will always be executed deferred * @default `false` */ defer?: boolean; diff --git a/src/components/super/i-block/decorators/wait.ts b/src/components/super/i-block/decorators/wait.ts index ad45a3db41..bd99e4f420 100644 --- a/src/components/super/i-block/decorators/wait.ts +++ b/src/components/super/i-block/decorators/wait.ts @@ -32,11 +32,12 @@ const waitCtxRgxp = /([^:]+):(\w+)/; * * @component() * class bExample extends iBlock { - * @wait({defer: true}}) + * @wait({defer: true}) * doSomething() { * // ... * return 42; * } + * } * ``` */ export function wait(opts: {defer: true} & WaitDecoratorOptions): Function; @@ -89,6 +90,7 @@ export function wait( * // ... * return 42; * } + * } * ``` */ export function wait(componentStatus: WaitStatuses, opts?: WaitDecoratorOptions): Function; @@ -154,7 +156,7 @@ export function wait( resolvedHandler = handler; const - getRoot = () => ctx != null ? this.field.get(ctx) : this, + getRoot = () => ctx != null ? this.field.get(ctx) : this, root = getRoot(); if (join === undefined) { @@ -175,7 +177,7 @@ export function wait( return res; - function exec(ctx: CanUndef) { + function exec(ctx: CanUndef) { const componentStatus = Number(statuses[that.componentStatus]); let diff --git a/src/components/super/i-block/i-block.ts b/src/components/super/i-block/i-block.ts index 3f6d46c447..8bfe3219cc 100644 --- a/src/components/super/i-block/i-block.ts +++ b/src/components/super/i-block/i-block.ts @@ -56,7 +56,8 @@ export { Classes, ModVal, ModsDecl, ModsProp, ModsDict }; @component() export default abstract class iBlock extends iBlockProviders { - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } @@ -72,7 +73,8 @@ export default abstract class iBlock extends iBlockProviders { stage: [] }; - protected override readonly $refs!: { + /** @inheritDoc */ + declare protected readonly $refs: { $el?: Element; }; diff --git a/src/components/super/i-block/interface.ts b/src/components/super/i-block/interface.ts index f2ab557f19..3b78534de4 100644 --- a/src/components/super/i-block/interface.ts +++ b/src/components/super/i-block/interface.ts @@ -62,40 +62,34 @@ export interface InitLoadCb { export interface UnsafeIBlock extends UnsafeComponentInterface { // @ts-ignore (access) - state: CTX['state']; + get state(): CTX['state']; // @ts-ignore (access) - storage: CTX['storage']; + get storage(): CTX['storage']; // @ts-ignore (access) - opt: CTX['opt']; + get opt(): CTX['opt']; // @ts-ignore (access) - dom: CTX['dom']; + get dom(): CTX['dom']; // @ts-ignore (access) block: CTX['block']; // @ts-ignore (access) - asyncRender: CTX['asyncRender']; + get moduleLoader(): CTX['moduleLoader']; // @ts-ignore (access) - moduleLoader: CTX['moduleLoader']; + get localEmitter(): CTX['localEmitter']; // @ts-ignore (access) - sync: CTX['sync']; + get parentEmitter(): CTX['parentEmitter']; // @ts-ignore (access) - localEmitter: CTX['localEmitter']; + get rootEmitter(): CTX['rootEmitter']; // @ts-ignore (access) - parentEmitter: CTX['parentEmitter']; - - // @ts-ignore (access) - rootEmitter: CTX['rootEmitter']; - - // @ts-ignore (access) - globalEmitter: CTX['globalEmitter']; + get globalEmitter(): CTX['globalEmitter']; // @ts-ignore (access) blockReadyListeners: CTX['blockReadyListeners']; @@ -126,7 +120,4 @@ export interface UnsafeIBlock extends UnsafeCompone // @ts-ignore (access) waitRef: CTX['waitRef']; - - // @ts-ignore (access) - createPropAccessors: CTX['createPropAccessors']; } diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 75f9d3bb3b..e5e7eb4ec0 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -11,8 +11,9 @@ * @packageDocumentation */ +import type { ModsProp, ModsDict } from 'core/component'; + import type iBlock from 'components/super/i-block/i-block'; -import type { ComponentInterface, ModsProp, ModsDict } from 'core/component'; export * from 'components/super/i-block/modules/mods/interface'; @@ -127,8 +128,8 @@ export function initMods(component: iBlock): ModsDict { * @param [link] - the reference name which takes its value based on the current field */ export function mergeMods( - component: iBlock, - oldComponent: iBlock, + component: iBlock['unsafe'], + oldComponent: iBlock['unsafe'], name: string, link?: string ): void { @@ -136,9 +137,7 @@ export function mergeMods( return; } - const - ctx = component.unsafe, - cache = ctx.$syncLinkCache.get(link); + const cache = component.$syncLinkCache.get(link); if (cache == null) { return; @@ -151,11 +150,11 @@ export function mergeMods( } const - modsProp = getExpandedModsProp(ctx), + modsProp = getExpandedModsProp(component), mods = {...oldComponent.mods}; Object.keys(mods).forEach((key) => { - if (ctx.sync.syncModCache[key]) { + if (component.sync.syncModCache[key] != null) { delete mods[key]; } }); @@ -167,24 +166,22 @@ export function mergeMods( l.sync(Object.assign(mods, modsProp)); } - function getExpandedModsProp(component: ComponentInterface): ModsDict { - const {unsafe} = component; - + function getExpandedModsProp(component: iBlock['unsafe']): ModsDict { if (link == null) { return {}; } - const modsProp = unsafe.$props[link]; + const modsProp = component.$props[link]; if (!Object.isDictionary(modsProp)) { return {}; } const - declMods = unsafe.meta.component.mods, + declMods = component.meta.component.mods, res = {...modsProp}; - Object.entries(unsafe.$attrs).forEach(([name, attr]) => { + Object.entries(component.$attrs).forEach(([name, attr]) => { if (name in declMods) { if (attr != null) { res[name] = attr; diff --git a/src/components/super/i-block/props.ts b/src/components/super/i-block/props.ts index e70a30ae13..533f1e1a73 100644 --- a/src/components/super/i-block/props.ts +++ b/src/components/super/i-block/props.ts @@ -14,6 +14,7 @@ import type { Stage } from 'components/super/i-block/interface'; import type { ModsProp } from 'components/super/i-block/modules/mods'; import { prop, DecoratorMethodWatcher } from 'components/super/i-block/decorators'; + import type { TransitionMethod } from 'components/base/b-router/interface'; @component({partial: 'iBlock'}) @@ -46,7 +47,7 @@ export default abstract class iBlockProps extends ComponentInterface { * For example, let's say we have a component that implements an image upload form. * And we have two options for this form: uploading from a link or uploading from a computer. * - * In order to differentiate between these two options and render different markups accordingly, + * To differentiate between these two options and render different markups accordingly, * we can create two stage values: "link" and "file". * This way, we can modify the component's template based on the current stage value. */ @@ -193,7 +194,7 @@ export default abstract class iBlockProps extends ComponentInterface { readonly routerStateUpdateMethod: Exclude = 'push'; /** - * A dictionary with remote component watchers. + * A dictionary containing remote component watchers. * Using this prop is very similar to using the @watch decorator: * 1. As a key, we specify the name of the current component method we want to call. * 2. As a value, we specify the property path or event that we want to watch or listen to. diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 539c02ce67..e98350cf6a 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -11,24 +11,24 @@ * @packageDocumentation */ +import config from 'config'; import symbolGenerator from 'core/symbol'; -import Provider, { providers, instanceCache, ProviderOptions } from 'core/data'; -import { unwrap as unwrapWatcher } from 'core/object/watch'; +import SyncPromise from 'core/promise/sync'; +import type { AsyncOptions } from 'core/async'; +import Provider, { providers, instanceCache, ProviderOptions } from 'core/data'; import { i18nFactory } from 'core/i18n'; -import SyncPromise from 'core/promise/sync'; -import config from 'config'; -import type { AsyncOptions } from 'core/async'; +import { unwrap as unwrapWatcher } from 'core/object/watch'; import { component } from 'core/component'; import type iData from 'components/super/i-data/i-data'; +import type iBlock from 'components/super/i-block/i-block'; import { statuses } from 'components/super/i-block/const'; import { system, hook } from 'components/super/i-block/decorators'; -import type iBlock from 'components/super/i-block/i-block'; import type { InitLoadCb, InitLoadOptions } from 'components/super/i-block/interface'; import iBlockState from 'components/super/i-block/state'; @@ -49,8 +49,7 @@ export default abstract class iBlockProviders extends iBlockState { return o.dontWaitRemoteProviders; } - const isRemote = /\bremote-provider\b/; - return !config.components[o.componentName]?.dependencies.some((dep) => isRemote.test(dep)); + return !config.components[o.componentName]?.dependencies.some((dep) => dep.includes('remote-provider')); } return val; @@ -81,8 +80,7 @@ export default abstract class iBlockProviders extends iBlockState { } } - const - that = this; + const that = this; if (!this.isActivated) { return; @@ -102,8 +100,7 @@ export default abstract class iBlockProviders extends iBlockState { return; } - const - {async: $a} = this; + const {async: $a} = this; const label = { label: $$.initLoad, @@ -144,8 +141,7 @@ export default abstract class iBlockProviders extends iBlockState { const initializing = this.nextTick(label).then((() => { this.$children.forEach((component) => { - const - status = component.componentStatus; + const status = component.componentStatus; if (!component.remoteProvider || !Object.isTruly(statuses[status])) { return; @@ -265,8 +261,7 @@ export default abstract class iBlockProviders extends iBlockState { * @param [opts] - additional options */ reload(opts?: InitLoadOptions): Promise { - const - res = this.initLoad(undefined, {silent: true, ...opts}); + const res = this.initLoad(undefined, {silent: true, ...opts}); if (Object.isPromise(res)) { return res; @@ -318,8 +313,7 @@ export default abstract class iBlockProviders extends iBlockState { remoteState: Object.cast(unwrapWatcher(this.remoteState)) }; - let - dp: Provider; + let dp: Provider; if (Object.isString(provider)) { const diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 76010d60a3..133993b618 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -12,26 +12,27 @@ */ import SyncPromise from 'core/promise/sync'; - import type Async from 'core/async'; + import type { BoundFn } from 'core/async'; +import type { Theme } from 'core/theme-manager'; -import { i18nFactory } from 'core/prelude/i18n'; -import { component, app, Hook, State } from 'core/component'; +import { i18nFactory } from 'core/i18n'; import { styles as hydratedStyles } from 'core/hydration-store'; +import { component, app, Hook, State } from 'core/component'; import type bRouter from 'components/base/b-router/b-router'; -import type iBlock from 'components/super/i-block/i-block'; import type { Module } from 'components/friends/module-loader'; import type { ConverterCallType } from 'components/friends/state'; -import { readyStatuses } from 'components/super/i-block/modules/activation'; +import { readyStatuses } from 'components/super/i-block/modules/activation'; import { field, system, computed, wait, hook, WaitDecoratorOptions } from 'components/super/i-block/decorators'; + +import type iBlock from 'components/super/i-block/i-block'; import type { Stage, ComponentStatus, ComponentStatuses } from 'components/super/i-block/interface'; import iBlockMods from 'components/super/i-block/mods'; -import type { Theme } from 'core/theme-manager'; @component({partial: 'iBlock'}) export default abstract class iBlockState extends iBlockMods { @@ -77,9 +78,8 @@ export default abstract class iBlockState extends iBlockMods { /** * If set to false, the component will not render its content during SSR. * - * In a hydration context, the field value is determined by the value of the `renderOnHydration` flag, - * which is stored in a `hydrationStore` during SSR for components that - * have the `ssrRenderingProp` value set to false. + * In a hydration context, the field value is determined by the value of the `renderOnHydration` flag, which + * is stored in a `hydrationStore` during SSR for components that have the `ssrRenderingProp` value set to false. * In other cases, the field value is derived from the `ssrRenderingProp` property. */ @field((o) => { @@ -129,6 +129,14 @@ export default abstract class iBlockState extends iBlockMods { return state; } + /** + * An API for working with the target document's URL + */ + @computed({cache: 'forever'}) + get location(): URL { + return this.remoteState.location; + } + /** * A string value indicating the initialization status of the component: * @@ -161,15 +169,14 @@ export default abstract class iBlockState extends iBlockMods { /** * Sets a new status for the component. * Note that not all statuses will trigger the component to re-render: - * statuses such as unloaded, inactive, and destroyed will only emit events. + * statuses such as `unloaded`, `inactive`, and `destroyed` will only emit events. * * @param value * @emits `componentStatus:{$value}(value: ComponentStatus, oldValue: ComponentStatus)` * @emits `componentStatusChange(value: ComponentStatus, oldValue: ComponentStatus)` */ set componentStatus(value: ComponentStatus) { - const - oldValue = this.componentStatus; + const oldValue = this.componentStatus; if (oldValue === value && value !== 'beforeReady') { return; @@ -250,8 +257,7 @@ export default abstract class iBlockState extends iBlockMods { * @emits `stageChange(value: CanUndef, oldValue: CanUndef)` */ set stage(value: CanUndef) { - const - oldValue = this.stage; + const oldValue = this.stage; if (oldValue === value) { return; @@ -312,7 +318,7 @@ export default abstract class iBlockState extends iBlockMods { protected shadowComponentStatusStore?: ComponentStatus; /** - * A string value that specifies the logical state in which the component should operate + * A string value specifying the logical state in which the component should operate * {@link iBlock.stageProp} */ @field({ @@ -326,21 +332,13 @@ export default abstract class iBlockState extends iBlockMods { protected stageStore?: Stage; /** - * A string value that indicates which lifecycle hook the component is currently in + * A string value indicating which lifecycle hook the component is currently in * * @see https://vuejs.org/guide/essentials/lifecycle.html * {@link iBlock.hook} */ protected hookStore: Hook = 'beforeRuntime'; - /** - * An API for working with the target document's URL - */ - @computed({cache: 'forever'}) - protected get location(): URL { - return this.remoteState.location; - } - /** * Switches the component to a new lifecycle hook * @@ -376,7 +374,7 @@ export default abstract class iBlockState extends iBlockMods { /** * A function aimed at internationalizing texts within traits. * Since traits are invoked within the context of components, the standard `i18n` does not operate, - * necessitating the explicit passing of the key set name (trait names). + * requiring the explicit passing of the key set name (trait names). * * @param traitName - the name of the trait * @param text - the text to be internationalized @@ -397,7 +395,6 @@ export default abstract class iBlockState extends iBlockMods { /** * Returns a promise that will be resolved when the component transitions to the specified component status - * * {@link Async.promise} * * @param status @@ -427,8 +424,7 @@ export default abstract class iBlockState extends iBlockMods { cbOrOpts?: F | WaitDecoratorOptions, opts?: WaitDecoratorOptions ): CanPromise> { - let - cb: CanUndef; + let cb: CanUndef; if (Object.isFunction(cbOrOpts)) { cb = cbOrOpts; @@ -443,8 +439,7 @@ export default abstract class iBlockState extends iBlockMods { return wait(status, {...opts, fn: cb}).call(this); } - let - isResolved = false; + let isResolved = false; const promise = new SyncPromise((resolve) => wait(status, { ...opts, @@ -468,7 +463,7 @@ export default abstract class iBlockState extends iBlockMods { */ @hook('created') hydrateStyles(componentName: string = this.componentName): void { - if (!SSR) { + if (!SSR || !HYDRATION) { return; } @@ -567,8 +562,7 @@ export default abstract class iBlockState extends iBlockMods { protected override initBaseAPI(): void { super.initBaseAPI(); - const - i = this.instance; + const i = this.instance; this.i18n = i.i18n.bind(this); this.syncStorageState = i.syncStorageState.bind(this); diff --git a/src/components/super/i-block/test/b-super-i-block-deactivation-dummy/b-super-i-block-deactivation-dummy.ts b/src/components/super/i-block/test/b-super-i-block-deactivation-dummy/b-super-i-block-deactivation-dummy.ts index 6a171f5369..fbd7bb9a4d 100644 --- a/src/components/super/i-block/test/b-super-i-block-deactivation-dummy/b-super-i-block-deactivation-dummy.ts +++ b/src/components/super/i-block/test/b-super-i-block-deactivation-dummy/b-super-i-block-deactivation-dummy.ts @@ -12,7 +12,8 @@ import iBlock, { component } from 'components/super/i-block/i-block'; @component() export default class bSuperIBlockDeactivationDummy extends iBlock { - protected override $refs!: iBlock['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iBlock['$refs'] & { button1: bButton; button2: bButton; }; diff --git a/src/components/super/i-block/test/b-super-i-block-mods-dummy/b-super-i-block-mods-dummy.ts b/src/components/super/i-block/test/b-super-i-block-mods-dummy/b-super-i-block-mods-dummy.ts index 15ae65eab2..b6d54edb81 100644 --- a/src/components/super/i-block/test/b-super-i-block-mods-dummy/b-super-i-block-mods-dummy.ts +++ b/src/components/super/i-block/test/b-super-i-block-mods-dummy/b-super-i-block-mods-dummy.ts @@ -24,7 +24,8 @@ export default class bSuperIBlockModsDummy extends bDummy { return this.$refs.dummy.mods; } - protected override readonly $refs!: bDummy['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: bDummy['$refs'] & { dummy: bDummy; }; } diff --git a/src/components/super/i-block/test/b-super-i-block-teleport-dummy/b-super-i-block-teleport-dummy.ts b/src/components/super/i-block/test/b-super-i-block-teleport-dummy/b-super-i-block-teleport-dummy.ts index 6f2de9f993..f14f1cd814 100644 --- a/src/components/super/i-block/test/b-super-i-block-teleport-dummy/b-super-i-block-teleport-dummy.ts +++ b/src/components/super/i-block/test/b-super-i-block-teleport-dummy/b-super-i-block-teleport-dummy.ts @@ -12,7 +12,8 @@ import iBlock, { component } from 'components/super/i-block/i-block'; @component() export default class bSuperIBlockTeleportDummy extends iBlock { - protected override $refs!: iBlock['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iBlock['$refs'] & { component: bButtonSlide; }; } diff --git a/src/components/super/i-block/test/b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts b/src/components/super/i-block/test/b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts index 487490b07f..21f5345d6f 100644 --- a/src/components/super/i-block/test/b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts +++ b/src/components/super/i-block/test/b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts @@ -53,7 +53,7 @@ export default class bSuperIBlockWatchDummy extends iData { return this.r.isAuth; } - @computed({cache: true, dependencies: ['complexObjStore']}) + @computed({cache: 'forever', dependencies: ['complexObjStore']}) get cachedComplexObj(): Dictionary { return Object.fastClone(this.complexObjStore); } diff --git a/src/components/super/i-data/README.md b/src/components/super/i-data/README.md index 59c9050883..58121ee482 100644 --- a/src/components/super/i-data/README.md +++ b/src/components/super/i-data/README.md @@ -170,7 +170,7 @@ interface MyData { @component() export default class bExample extends iData { - override readonly DB!: MyData; + declare readonly DB: MyData; override dataProviderProp: DataProviderProp = 'User'; @@ -357,7 +357,7 @@ import iData, { component, field, TitleValue, RequestParams, DataProviderProp } @component() export default class bExample extends iData { - override readonly DB!: Data; + declare readonly DB: Data; override readonly dataProviderProp: DataProviderProp = 'api.User'; @@ -382,7 +382,7 @@ export default class bExample extends iData { ['wait', 'canRequestData'] ])) - protected override readonly requestParams!: RequestParams; + declare readonly requestParams: RequestParams; /** * Returns true if the component can load remote data diff --git a/src/components/super/i-data/i-data.ts b/src/components/super/i-data/i-data.ts index efbc2dd819..b403ce3d82 100644 --- a/src/components/super/i-data/i-data.ts +++ b/src/components/super/i-data/i-data.ts @@ -59,7 +59,8 @@ const @component({functional: null}) export default abstract class iData extends iDataHandlers { - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/super/i-data/interface.ts b/src/components/super/i-data/interface.ts index 6478e33c6d..4721806b1e 100644 --- a/src/components/super/i-data/interface.ts +++ b/src/components/super/i-data/interface.ts @@ -45,7 +45,6 @@ export type CheckDBEquality = boolean | CheckDBEqualityFn; -// @ts-ignore (extend) export interface UnsafeIData extends UnsafeIBlock { // @ts-ignore (access) dbStore: CTX['dbStore']; diff --git a/src/components/super/i-input-text/i-input-text.ts b/src/components/super/i-input-text/i-input-text.ts index ec844ba6a9..86ce7b66c0 100644 --- a/src/components/super/i-input-text/i-input-text.ts +++ b/src/components/super/i-input-text/i-input-text.ts @@ -189,7 +189,8 @@ export default class iInputText extends iInput implements iWidth, iSize { @prop({type: Object, required: false}) readonly regExps?: Dictionary; - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } @@ -258,7 +259,8 @@ export default class iInputText extends iInput implements iWidth, iSize { @system((o) => new Mask(o)) protected maskAPI!: Mask; - protected override readonly $refs!: iInput['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iInput['$refs'] & { input: HTMLInputElement; }; diff --git a/src/components/super/i-input-text/mask/class.ts b/src/components/super/i-input-text/mask/class.ts index a540950252..341389fe1c 100644 --- a/src/components/super/i-input-text/mask/class.ts +++ b/src/components/super/i-input-text/mask/class.ts @@ -41,7 +41,8 @@ interface Mask { ) class Mask extends Friend { - override readonly C!: iInputText; + /** @inheritDoc */ + declare readonly C: iInputText; /** * The compiled mask diff --git a/src/components/super/i-input/README.md b/src/components/super/i-input/README.md index cf53d7f6db..5d1e1a68b4 100644 --- a/src/components/super/i-input/README.md +++ b/src/components/super/i-input/README.md @@ -48,7 +48,7 @@ import iInput, { component } from 'components/super/i-input/i-input'; @component() export default class MyInput extends iInput { - protected override readonly $refs!: {input: HTMLInputElement}; + declare protected readonly $refs: iInput['$refs'] & {input: HTMLInputElement}; } ``` @@ -64,9 +64,9 @@ import iInput, { component } from 'components/super/i-input/i-input'; @component() export default class MyInput extends iInput { - override readonly Value!: string; + declare readonly Value: string; - override readonly FormValue!: Number; + declare readonly FormValue: Number; override readonly dataType: Function = parseInt; @@ -348,9 +348,9 @@ import iInput, { component } from 'components/super/i-input/i-input'; @component() export default class MyInput extends iInput { - override readonly Value!: string; + declare readonly Value: string; - override readonly FormValue!: Number; + declare readonly FormValue: Number; override readonly dataType: Function = parseInt; diff --git a/src/components/super/i-input/fields.ts b/src/components/super/i-input/fields.ts index d48a036495..ccc4d0de2b 100644 --- a/src/components/super/i-input/fields.ts +++ b/src/components/super/i-input/fields.ts @@ -371,7 +371,8 @@ export default abstract class iInputFields extends iInputProps { @system((o) => o.sync.link()) protected errorStore?: string; - protected override readonly $refs!: iInputProps['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iInputProps['$refs'] & { input?: HTMLInputElement; }; diff --git a/src/components/super/i-input/i-input.ts b/src/components/super/i-input/i-input.ts index a5f101fbdc..08628170b4 100644 --- a/src/components/super/i-input/i-input.ts +++ b/src/components/super/i-input/i-input.ts @@ -88,7 +88,8 @@ export default abstract class iInput extends iInputHandlers implements iVisible, ] }; - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/super/i-page/README.md b/src/components/super/i-page/README.md index 5779477fa7..97bd2bbafa 100644 --- a/src/components/super/i-page/README.md +++ b/src/components/super/i-page/README.md @@ -68,11 +68,10 @@ it will be called (the result will be used as the title). ```typescript class bMyPage extends iPage { - /** @override */ - stagePageTitles: StageTitles = { + override readonly stagePageTitles: StageTitles = { '[[DEFAULT]]': 'Default title', profile: 'Profile page' - } + }; toProfile(): void { this.stage = 'profile'; @@ -88,11 +87,10 @@ The current page title. ```typescript class bMyPage extends iPage { - /** @override */ - stagePageTitles: StageTitles = { + override readonly stagePageTitles: StageTitles = { '[[DEFAULT]]': 'Default title', profile: 'Profile page' - } + }; toProfile(): void { console.log(this.title === 'Default title'); diff --git a/src/components/super/i-page/i-page.ts b/src/components/super/i-page/i-page.ts index adce23223a..af03912ee6 100644 --- a/src/components/super/i-page/i-page.ts +++ b/src/components/super/i-page/i-page.ts @@ -96,7 +96,7 @@ export default abstract class iPage extends iData implements iVisible { * * {@link iPage.scrollTo} */ - @computed({cache: true}) + @computed({cache: 'forever'}) get scrollToProxy(): this['scrollTo'] { return (...args) => { this.async.setImmediate(() => this.scrollTo(...args), { diff --git a/src/components/super/i-page/interface.ts b/src/components/super/i-page/interface.ts index 8aeef3c570..b50ca5b64d 100644 --- a/src/components/super/i-page/interface.ts +++ b/src/components/super/i-page/interface.ts @@ -8,16 +8,16 @@ import type iPage from 'components/super/i-page/i-page'; -export type TitleValue = +export type TitleValue = string | ((ctx: CTX) => string); -export type DescriptionValue = TitleValue; +export type DescriptionValue = TitleValue; -export type TitleValueProp = TitleValue | (() => TitleValue); -export type DescriptionValueProp = TitleValueProp; +export type TitleValueProp = TitleValue | (() => TitleValue); +export type DescriptionValueProp = TitleValueProp; -export interface StageTitles extends Dictionary> { +export interface StageTitles extends Dictionary> { '[[DEFAULT]]': TitleValue; } diff --git a/src/components/super/i-static-page/i-static-page.ts b/src/components/super/i-static-page/i-static-page.ts index 08127ce9ae..9a1a077168 100644 --- a/src/components/super/i-static-page/i-static-page.ts +++ b/src/components/super/i-static-page/i-static-page.ts @@ -115,8 +115,9 @@ export default abstract class iStaticPage extends iPage { * This field is based on the one with the same name from `remoteState`. * It is used for convenience. */ - @system((o) => o.sync.link('remoteState.lastOnlineDate')) - lastOnlineDate?: Date; + get lastOnlineDate(): Date { + return this.remoteState.lastOnlineDate!; + } /** * Initial value for the active route. @@ -135,7 +136,7 @@ export default abstract class iStaticPage extends iPage { /** * The name of the active route page */ - @computed({cache: true, dependencies: ['route.meta.name']}) + @computed({dependencies: ['route.meta.name']}) get activePage(): CanUndef { return this.field.get('route.meta.name'); } diff --git a/src/components/traits/i-active-items/i-active-items.ts b/src/components/traits/i-active-items/i-active-items.ts index 92ea6c41ba..0b34349b9a 100644 --- a/src/components/traits/i-active-items/i-active-items.ts +++ b/src/components/traits/i-active-items/i-active-items.ts @@ -19,7 +19,7 @@ import type { Active, ActiveProp, Item } from 'components/traits/i-active-items/ export * from 'components/traits/i-items/i-items'; export * from 'components/traits/i-active-items/interface'; -type TraitComponent = iBlock & iActiveItems; +type TraitComponent = (iBlock | iBlock['unsafe']) & iActiveItems; export default abstract class iActiveItems extends iItems { /** {@link iItems.Item} */ @@ -168,7 +168,7 @@ export default abstract class iActiveItems extends iItems { * @param ctx */ static initActiveStoreListeners(ctx: TraitComponent): void { - ctx.watch('activeStore', {deep: ctx.multiple}, (value) => { + ctx.unsafe.watch('activeStore', {deep: ctx.multiple}, (value) => { ctx.emit(ctx.activeChangeEvent, value); }); } diff --git a/src/components/traits/i-history/history/index.ts b/src/components/traits/i-history/history/index.ts index cf950c25d3..6b3bb7a09c 100644 --- a/src/components/traits/i-history/history/index.ts +++ b/src/components/traits/i-history/history/index.ts @@ -26,7 +26,8 @@ const $$ = symbolGenerator(); export default class History extends Friend { - override readonly C!: iHistory; + /** @inheritDoc */ + declare readonly C: iHistory; /** * Default configuration for a history item diff --git a/src/core/component/const/cache.ts b/src/core/component/const/cache.ts index 79e6c4ee2b..a86fb2e809 100644 --- a/src/core/component/const/cache.ts +++ b/src/core/component/const/cache.ts @@ -16,6 +16,7 @@ import type { import type { ComponentMeta, + ComponentOptions, ComponentConstructorInfo, diff --git a/src/core/component/context/index.ts b/src/core/component/context/index.ts index 30dae7a2f9..6a03f0d1f9 100644 --- a/src/core/component/context/index.ts +++ b/src/core/component/context/index.ts @@ -16,17 +16,45 @@ import type { ComponentInterface } from 'core/component/interface'; export * from 'core/component/context/const'; +type ComponentContext = Dictionary & ComponentInterface; +type UnsafeComponentContext = Dictionary & ComponentInterface['unsafe']; + +interface ComponentContextStructure { + ctx: ComponentContext; + unsafe: UnsafeComponentContext; +} + /** * Returns a wrapped component context object based on the passed one. * This function allows developers to override component properties and methods without altering the original object. + * * Essentially, override creates a new object that contains the original object as its prototype, * allowing for the addition, modification, or removal of properties and methods without affecting the original object. * * @param component */ -export function getComponentContext(component: object): Dictionary & ComponentInterface['unsafe'] { +export function getComponentContext(component: object): ComponentContext; + +/** + * This override allows returning an unsafe interface + * + * @param component + * @param unsafe + */ +export function getComponentContext(component: object, unsafe: true): ComponentContextStructure; + +export function getComponentContext( + component: object, + unsafe?: true +): ComponentContext | ComponentContextStructure { if (toRaw in component) { - return Object.cast(component); + const ctx = Object.cast(component); + + if (unsafe) { + return {ctx, unsafe: Object.cast(ctx)}; + } + + return ctx; } if (!(toWrapped in component)) { @@ -34,7 +62,13 @@ export function getComponentContext(component: object): Dictionary & ComponentIn saveRawComponentContext(wrappedCtx, component); } - return component[toWrapped]; + const ctx = Object.cast(component[toWrapped]); + + if (unsafe) { + return {ctx, unsafe: Object.cast(ctx)}; + } + + return ctx; } /** diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index ab7b5f1b52..884d83fe77 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -143,7 +143,7 @@ export function paramsFactory( }); } else { - hooks[hook] = wrapOpts({name, hook}); + hooks[hook] = wrapOpts({name, hook}); } }); } diff --git a/src/core/component/decorators/prop/test/b-effect-prop-wrapper-dummy/b-effect-prop-wrapper-dummy.ts b/src/core/component/decorators/prop/test/b-effect-prop-wrapper-dummy/b-effect-prop-wrapper-dummy.ts index 644ce4ee0c..2b6595b1a7 100644 --- a/src/core/component/decorators/prop/test/b-effect-prop-wrapper-dummy/b-effect-prop-wrapper-dummy.ts +++ b/src/core/component/decorators/prop/test/b-effect-prop-wrapper-dummy/b-effect-prop-wrapper-dummy.ts @@ -25,7 +25,8 @@ export default class bEffectPropWrapperDummy extends bDummy { return this.$refs.child; } - protected override readonly $refs!: bDummy['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: bDummy['$refs'] & { child: bNonEffectPropDummy; }; } diff --git a/src/core/component/directives/helpers.ts b/src/core/component/directives/helpers.ts index 9d63b7fd95..e21c500070 100644 --- a/src/core/component/directives/helpers.ts +++ b/src/core/component/directives/helpers.ts @@ -61,11 +61,10 @@ export function getDirectiveComponent(vnode: Nullable): CanNull; slotComponent?: CanArray; nestedSlotComponent?: CanArray; diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index ca9353ddef..5c25a1dc72 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -37,23 +37,22 @@ export function getComponent(meta: ComponentMeta): ComponentOptions { // eslint-disable-next-line @v4fire/unbound-method - const {unwatch} = watch(ctx.$fields, {deep: true, immediate: true}, handler); + const {unwatch} = watch(unsafe.$fields, {deep: true, immediate: true}, handler); return unwatch; }; - ctx.$async.on(emitter, 'mutation', watcher, { + unsafe.$async.on(emitter, 'mutation', watcher, { group: 'watchers:suspend' }); - return SSR ? {} : ctx.$fields; + return SSR ? {} : unsafe.$fields; function watcher(value: unknown, oldValue: unknown, info: WatchHandlerParams): void { const @@ -68,7 +67,7 @@ export function getComponent(meta: ComponentMeta): ComponentOptions ctx.$forceUpdate(), {label: 'forceUpdate'}); + unsafe.$async.setImmediate(() => ctx.$forceUpdate(), {label: 'forceUpdate'}); } } }, @@ -78,23 +77,17 @@ export function getComponent(meta: ComponentMeta): ComponentOptions { - const init = ctx.$initializer; + const init = unsafe.$initializer; try { // If init is a synchronous promise, we explicitly perform an `unwrap` to eliminate the extra microtask diff --git a/src/core/component/field/helpers.ts b/src/core/component/field/helpers.ts index c539e0c1b4..d8ca4e133e 100644 --- a/src/core/component/field/helpers.ts +++ b/src/core/component/field/helpers.ts @@ -26,11 +26,9 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar return 0; } - const - {after} = field; + const {after} = field; - let - weight = 0; + let weight = 0; if (after != null) { weight += after.size; @@ -59,8 +57,7 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar * @param fields */ export function sortFields(fields: Dictionary): SortedFields { - let - val = sortedFields.get(fields); + let val = sortedFields.get(fields); if (val == null) { val = Object.entries(Object.cast>(fields)).sort(([_1, a], [_2, b]) => { diff --git a/src/core/component/field/init.ts b/src/core/component/field/init.ts index c27d610cf9..6570d401c2 100644 --- a/src/core/component/field/init.ts +++ b/src/core/component/field/init.ts @@ -11,7 +11,7 @@ import type { ComponentInterface, ComponentField } from 'core/component/interfac /** * Initializes all fields of a given component instance. - * This function returns a dictionary that contains the names of the initialized fields as keys, + * This function returns A dictionary containing the names of the initialized fields as keys, * with their corresponding initialized values as values. * * @param from - the dictionary where is stored the passed component fields, like `$fields` or `$systemFields` @@ -32,12 +32,10 @@ export function initFields( instance } = unsafe.meta; - const - isFunctional = params.functional === true; + const isFunctional = params.functional === true; sortFields(from).forEach(([name, field]) => { - const - sourceVal = store[name]; + const sourceVal = store[name]; const canSkip = field == null || sourceVal !== undefined || @@ -69,8 +67,6 @@ export function initFields( } else { store[name] = val; } - - unsafe.$activeField = undefined; }); return store; diff --git a/src/core/component/functional/context.ts b/src/core/component/functional/context.ts index afb1759915..d6affd0309 100644 --- a/src/core/component/functional/context.ts +++ b/src/core/component/functional/context.ts @@ -101,7 +101,7 @@ export function createVirtualContext( }; } - const virtualCtx = Object.cast({ + const virtualCtx = Object.cast({ componentName: meta.componentName, meta, @@ -133,6 +133,8 @@ export function createVirtualContext( } }); + const unsafe = Object.cast(virtualCtx); + // When extending the context of the original component (e.g., Vue), to avoid conflicts, // we create an object with the original context in the prototype: `V4Context<__proto__: OriginalContext>`. // However, for functional components, this approach is redundant and can lead to memory leaks. @@ -152,10 +154,10 @@ export function createVirtualContext( handlers.forEach(([event, once, handler]) => { if (once) { - virtualCtx.$once(event, handler); + unsafe.$once(event, handler); } else { - virtualCtx.$on(event, handler); + unsafe.$on(event, handler); } }); diff --git a/src/core/component/functional/helpers.ts b/src/core/component/functional/helpers.ts index 27b3acf7a4..935976d6fe 100644 --- a/src/core/component/functional/helpers.ts +++ b/src/core/component/functional/helpers.ts @@ -31,7 +31,7 @@ export function initDynamicComponentLifeCycle(component: ComponentInterface): Co case 'updated': { inheritContext(unsafe, node.component); - init.createdState(unsafe); + init.createdState(component); mount(); break; } @@ -51,7 +51,7 @@ export function initDynamicComponentLifeCycle(component: ComponentInterface): Co // Performs a mount on the next tick to ensure that the component is rendered // and all adjacent re-renders have collapsed - unsafe.$async.nextTick().then(() => init.mountedState(unsafe)).catch(stderr); + unsafe.$async.nextTick().then(() => init.mountedState(component)).catch(stderr); } } } @@ -120,7 +120,7 @@ export function inheritContext( ( Object.isFunction(field.unique) ? - !Object.isTruly(field.unique(ctx, parentCtx)) : + !Object.isTruly(field.unique(ctx, Object.cast(parentCtx))) : !field.unique ) && @@ -147,7 +147,7 @@ export function inheritContext( ctx[name] = newVal; } else if (Object.isFunction(field.merge)) { - field.merge(ctx, parentCtx, name, link); + field.merge(ctx, Object.cast(parentCtx), name, link); } } else { diff --git a/src/core/component/functional/test/b-functional-dummy/b-functional-dummy.ts b/src/core/component/functional/test/b-functional-dummy/b-functional-dummy.ts index 52d18b1cb6..c82d0f5158 100644 --- a/src/core/component/functional/test/b-functional-dummy/b-functional-dummy.ts +++ b/src/core/component/functional/test/b-functional-dummy/b-functional-dummy.ts @@ -23,7 +23,8 @@ export default class bFunctionalDummy extends bDummy { @system() counterStore: number = 0; - protected override $refs!: bDummy['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: bDummy['$refs'] & { button?: bFunctionalButtonDummy; gettersDummy?: bFunctionalGettersDummy; }; diff --git a/src/core/component/functional/test/b-functional-getters-dummy/b-functional-getters-dummy.ts b/src/core/component/functional/test/b-functional-getters-dummy/b-functional-getters-dummy.ts index 954934c65f..7a6b9bf465 100644 --- a/src/core/component/functional/test/b-functional-getters-dummy/b-functional-getters-dummy.ts +++ b/src/core/component/functional/test/b-functional-getters-dummy/b-functional-getters-dummy.ts @@ -25,7 +25,8 @@ export default class bFunctionalGettersDummy extends bDummy { @system({merge: true}) logStore: string[] = []; - protected override $refs!: bDummy['$refs'] & { + /** @inheritDoc */ + protected declare $refs: bDummy['$refs'] & { container: HTMLElement; }; diff --git a/src/core/component/hook/index.ts b/src/core/component/hook/index.ts index c9f28b8915..9d6987a697 100644 --- a/src/core/component/hook/index.ts +++ b/src/core/component/hook/index.ts @@ -18,7 +18,7 @@ import type { Hook, ComponentHook, ComponentInterface } from 'core/component/int /** * Runs a hook on the specified component instance. - * The function returns a promise that is resolved when all hook handlers are executed. + * The function returns a promise resolved when all hook handlers are executed. * * @param hook - the hook name to run * @param component - the tied component instance diff --git a/src/core/component/init/README.md b/src/core/component/init/README.md index 2239ad9827..97c988e9d6 100644 --- a/src/core/component/init/README.md +++ b/src/core/component/init/README.md @@ -6,8 +6,9 @@ Basically, this API is used by adaptors of component libraries, and you don't ne ## Registering a component V4Fire provides the ability to describe components using native JavaScript classes and decorators, -instead of using the API of the component library being used. But in order to convert a component from a class form -to a form understood by the used component library, it is necessary to register the component. +instead of using the API of the component library being used. +But to convert a component from a class form to a form understood by the used component library, +it is necessary to register the component. Registering a component in V4Fire is a lazy and one-time operation. That is, the component is only registered when it is actually used in the template. Once a component has been registered once, it can already be used diff --git a/src/core/component/init/component.ts b/src/core/component/init/component.ts index 342984d05e..fae4173e5f 100644 --- a/src/core/component/init/component.ts +++ b/src/core/component/init/component.ts @@ -15,7 +15,7 @@ import type { ComponentConstructorInfo } from 'core/component/reflect'; * Registers parent components for the given one. * The function returns false if all parent components are already registered. * - * This function is needed because we have lazy component registration: when we see the "foo" component for + * This function is necessary because we have lazy component registration: when we see the "foo" component for * the first time in the template, we need to check the registration of all its parent components. * * @param component - the component information object @@ -60,7 +60,7 @@ export function registerParentComponents(component: ComponentConstructorInfo): b * The function returns the metaobject of the created component, or undefined if the component isn't found. * If the component is already registered, it won't be registered twice. * - * This function is needed because we have lazy component registration. + * This function is necessary because we have lazy component registration. * Keep in mind that you must call `registerParentComponents` before calling this function. * * @param name - the component name diff --git a/src/core/component/init/states/activated.ts b/src/core/component/init/states/activated.ts index 993bd91a3c..e7bbb8e67f 100644 --- a/src/core/component/init/states/activated.ts +++ b/src/core/component/init/states/activated.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 7623e166f7..9900969e3c 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -50,9 +50,13 @@ export function beforeCreateState( unsafe.unsafe = unsafe; unsafe.componentName = meta.componentName; - unsafe.meta = meta; - unsafe.instance = Object.cast(meta.instance); + + Object.defineProperty(unsafe, 'instance', { + configurable: true, + enumerable: true, + get: () => meta.instance + }); unsafe.$fields = {}; unsafe.$systemFields = {}; @@ -198,8 +202,7 @@ export function beforeCreateState( unsafe.$normalParent = getNormalParent(component); ['$root', '$parent', '$normalParent'].forEach((key) => { - const - val = unsafe[key]; + const val = unsafe[key]; if (val != null) { Object.defineProperty(unsafe, key, { @@ -215,8 +218,7 @@ export function beforeCreateState( configurable: true, enumerable: true, get() { - const - {$el} = unsafe; + const {$el} = unsafe; // If the component node is null or a node that cannot have children (such as a text or comment node) if ($el?.querySelectorAll == null) { @@ -274,6 +276,7 @@ export function beforeCreateState( }); attachAttrPropsListeners(component); + attachAccessorsFromMeta(component); runHook('beforeRuntime', component).catch(stderr); diff --git a/src/core/component/init/states/before-data-create.ts b/src/core/component/init/states/before-data-create.ts index 19b87b1ab4..3c2a1c1c27 100644 --- a/src/core/component/init/states/before-data-create.ts +++ b/src/core/component/init/states/before-data-create.ts @@ -14,7 +14,6 @@ import type { ComponentInterface } from 'core/component/interface'; /** * Initializes the "beforeDataCreate" state to the specified component instance - * * @param component */ export function beforeDataCreateState(component: ComponentInterface): void { diff --git a/src/core/component/init/states/before-destroy.ts b/src/core/component/init/states/before-destroy.ts index 8796f496d7..c7e8437d50 100644 --- a/src/core/component/init/states/before-destroy.ts +++ b/src/core/component/init/states/before-destroy.ts @@ -8,10 +8,11 @@ import * as gc from 'core/component/gc'; +import { runHook } from 'core/component/hook'; + import { dropRawComponentContext } from 'core/component/context'; import { callMethodFromComponent } from 'core/component/method'; -import { runHook } from 'core/component/hook'; import { destroyedHooks } from 'core/component/const'; import type { ComponentInterface, ComponentDestructorOptions } from 'core/component/interface'; diff --git a/src/core/component/init/states/before-mount.ts b/src/core/component/init/states/before-mount.ts index ee02925c26..72d1be53a3 100644 --- a/src/core/component/init/states/before-mount.ts +++ b/src/core/component/init/states/before-mount.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** @@ -16,8 +17,7 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function beforeMountState(component: ComponentInterface): void { - const - {$el} = component; + const {$el} = component; if ($el != null) { $el.component = component; diff --git a/src/core/component/init/states/before-update.ts b/src/core/component/init/states/before-update.ts index 1eedf5d226..45e09ef9d0 100644 --- a/src/core/component/init/states/before-update.ts +++ b/src/core/component/init/states/before-update.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** diff --git a/src/core/component/init/states/created.ts b/src/core/component/init/states/created.ts index 3034506b75..4798e0e72d 100644 --- a/src/core/component/init/states/created.ts +++ b/src/core/component/init/states/created.ts @@ -8,14 +8,13 @@ import { unmute } from 'core/object/watch'; +import { runHook } from 'core/component/hook'; import { destroyedHooks } from 'core/component/const'; import { callMethodFromComponent } from 'core/component/method'; -import { runHook } from 'core/component/hook'; import type { ComponentDestructorOptions, ComponentInterface, Hook } from 'core/component/interface'; -const - remoteActivationLabel = Symbol('The remote activation label'); +const remoteActivationLabel = Symbol('The remote activation label'); /** * Initializes the "created" state to the specified component instance @@ -87,8 +86,7 @@ export function createdState(component: ComponentInterface): void { }); }; - const - normalParent = unsafe.$normalParent!.unsafe; + const normalParent = unsafe.$normalParent!.unsafe; if (activationHooks[normalParent.hook] != null) { onActivation(normalParent.hook); diff --git a/src/core/component/init/states/deactivated.ts b/src/core/component/init/states/deactivated.ts index 39957d33f8..e81c490121 100644 --- a/src/core/component/init/states/deactivated.ts +++ b/src/core/component/init/states/deactivated.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** diff --git a/src/core/component/init/states/destroyed.ts b/src/core/component/init/states/destroyed.ts index 313e52461b..6453d5a760 100644 --- a/src/core/component/init/states/destroyed.ts +++ b/src/core/component/init/states/destroyed.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** diff --git a/src/core/component/init/states/error-captured.ts b/src/core/component/init/states/error-captured.ts index 09dd29932d..a356706ef1 100644 --- a/src/core/component/init/states/error-captured.ts +++ b/src/core/component/init/states/error-captured.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** diff --git a/src/core/component/init/states/mounted.ts b/src/core/component/init/states/mounted.ts index e1aa235bf7..65ab733e1e 100644 --- a/src/core/component/init/states/mounted.ts +++ b/src/core/component/init/states/mounted.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** diff --git a/src/core/component/init/states/render-trigered.ts b/src/core/component/init/states/render-trigered.ts index d82b684f1c..f4ca9020f3 100644 --- a/src/core/component/init/states/render-trigered.ts +++ b/src/core/component/init/states/render-trigered.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** diff --git a/src/core/component/init/states/updated.ts b/src/core/component/init/states/updated.ts index e084a09d02..51f32f80d5 100644 --- a/src/core/component/init/states/updated.ts +++ b/src/core/component/init/states/updated.ts @@ -6,9 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; +import { callMethodFromComponent } from 'core/component/method'; + import type { ComponentInterface } from 'core/component/interface'; /** diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index 99e81999b0..b4dda2e680 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -48,17 +48,18 @@ export abstract class ComponentInterface { /** * The unique component identifier. + * * The value for this prop is automatically generated during the build process, * but it can also be manually specified. * If the prop is not provided, the ID will be generated at runtime. */ - readonly componentIdProp?: string; + abstract readonly componentIdProp?: string; /** * The unique component identifier. * The value is formed based on the passed prop or dynamically. */ - readonly componentId!: string; + abstract readonly componentId: string; /** * The component name in dash-style without special postfixes like `-functional` @@ -69,7 +70,7 @@ export abstract class ComponentInterface { * The unique or global name of the component. * Used to synchronize component data with various external storages. */ - readonly globalName?: string; + abstract readonly globalName?: string; /** * True if the component renders as a regular one, but can be rendered as a functional. @@ -176,7 +177,7 @@ export abstract class ComponentInterface { * causing TypeScript errors. * Use it when you need to decompose the component class into a composition of friendly classes. */ - get unsafe(): UnsafeGetter> { + get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/core/component/interface/component/unsafe.ts b/src/core/component/interface/component/unsafe.ts index ded8a4ba3f..2c07466b27 100644 --- a/src/core/component/interface/component/unsafe.ts +++ b/src/core/component/interface/component/unsafe.ts @@ -10,21 +10,18 @@ import type { ComponentInterface } from 'core/component/interface/component/comp /** * A helper structure to pack the unsafe interface. - * It fixes some ambiguous TS warnings. + * It resolves some ambiguous TS warnings. */ -export type UnsafeGetter = - Dictionary & U['CTX'] & U & {unsafe: any}; +export type UnsafeGetter< + CTX extends ComponentInterface, + U extends UnsafeComponentInterface = UnsafeComponentInterface +> = Dictionary & Overwrite & {unsafe: any}; /** * This is a special interface that provides access to protected properties and methods outside the primary class. * It is used to create friendly classes. */ export interface UnsafeComponentInterface { - /** - * Type: the context type - */ - readonly CTX: CTX; - // @ts-ignore (access) meta: CTX['meta']; @@ -38,11 +35,8 @@ export interface UnsafeComponentInterface; - $renderCounter: number; - // @ts-ignore (access) $attrs: CTX['$attrs']; @@ -85,12 +79,6 @@ export interface UnsafeComponentInterface { } /** - * A map that contains all registered links + * A map containing all registered links */ export type SyncLinkCache = Map< string | object, diff --git a/src/core/component/interface/mod.ts b/src/core/component/interface/mod.ts index 1100909685..3411c145a7 100644 --- a/src/core/component/interface/mod.ts +++ b/src/core/component/interface/mod.ts @@ -26,17 +26,17 @@ export type ModDeclVal = CanArray; export type ExpandedModDeclVal = ModDeclVal | typeof PARENT; /** - * A dictionary that contains modifiers to pass to the component + * A dictionary containing modifiers to pass to the component */ export type ModsProp = Dictionary; /** - * A dictionary that contains normalized modifiers + * A dictionary containing normalized modifiers */ export type ModsDict = Dictionary>; /** - * A dictionary that contains predefined modifiers and their possible values + * A dictionary containing predefined modifiers and their possible values * * @example * ```typescript diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index a96ad4e4fd..f3d56b6f51 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -85,22 +85,21 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { cache = new Map(); meta.component[SSR ? 'ssrRender' : 'render'] = Object.cast((ctx: object, ...args: unknown[]) => { - const - unsafe = getComponentContext(ctx), - result = callRenderFunction(); + const {unsafe} = getComponentContext(ctx, true); + + const res = callRenderFunction(); // @ts-ignore (unsafe) unsafe['$renderCounter'] = unsafe.$renderCounter + 1; - return result; + return res; function callRenderFunction() { if (cache.has(ctx)) { return cache.get(ctx)(); } - const - render = meta.methods.render!.fn.call(unsafe, unsafe, ...args); + const render = meta.methods.render!.fn.call(unsafe, unsafe, ...args); if (!Object.isFunction(render)) { return render; diff --git a/src/core/component/meta/interface/index.ts b/src/core/component/meta/interface/index.ts index b0f106d068..43d572f181 100644 --- a/src/core/component/meta/interface/index.ts +++ b/src/core/component/meta/interface/index.ts @@ -60,7 +60,7 @@ export interface ComponentMeta { instance: Dictionary; /** - * A dictionary that contains the parameters provided to the `@component` decorator for the component + * A dictionary containing the parameters provided to the `@component` decorator for the component */ params: ComponentOptions; @@ -70,12 +70,12 @@ export interface ComponentMeta { parentMeta: CanNull; /** - * A dictionary that contains the input properties (props) for the component + * A dictionary containing the input properties (props) for the component */ props: Dictionary; /** - * A dictionary that contains the available component modifiers. + * A dictionary containing the available component modifiers. * Modifiers are a way to alter the behavior or appearance of a component without changing its underlying * functionality. * They can be used to customize components for specific use cases, or to extend their capabilities. @@ -84,12 +84,12 @@ export interface ComponentMeta { mods: ModsDecl; /** - * A dictionary that contains the component fields that can trigger a re-rendering of the component + * A dictionary containing the component fields that can trigger a re-rendering of the component */ fields: Dictionary; /** - * A dictionary that contains the component fields that do not cause a re-rendering of the component when they change. + * A dictionary containing the component fields that do not cause a re-rendering of the component when they change. * These fields are typically used for internal bookkeeping or for caching computed values, * and do not affect the visual representation of the component. * Examples include variables used for storing data or for tracking the component's internal state, @@ -100,24 +100,24 @@ export interface ComponentMeta { systemFields: Dictionary; /** - * A dictionary that contains the component fields that have a "Store" postfix in their name + * A dictionary containing the component fields that have a "Store" postfix in their name */ tiedFields: Dictionary; /** - * A dictionary that contains the accessor methods of the component that support caching or watching + * A dictionary containing the accessor methods of the component that support caching or watching */ computedFields: Dictionary; /** - * A dictionary that contains the simple component accessors, + * A dictionary containing the simple component accessors, * which are typically used for retrieving or modifying the value of a non-reactive property * that does not require caching or watching */ accessors: Dictionary; /** - * A dictionary that contains the component methods + * A dictionary containing the component methods */ methods: Dictionary; @@ -127,17 +127,17 @@ export interface ComponentMeta { watchers: Dictionary; /** - * A dictionary that contains the component dependencies to watch to invalidate the cache of computed fields + * A dictionary containing the component dependencies to watch to invalidate the cache of computed fields */ watchDependencies: ComponentWatchDependencies; /** - * A dictionary that contains the component prop dependencies to watch to invalidate the cache of computed fields + * A dictionary containing the component prop dependencies to watch to invalidate the cache of computed fields */ watchPropDependencies: ComponentWatchPropDependencies; /** - * A dictionary that contains the component hook listeners, + * A dictionary containing the component hook listeners, * which are essentially functions that are executed at specific stages in the V4Fire component's lifecycle */ hooks: ComponentHooks; @@ -164,27 +164,27 @@ export interface ComponentMeta { attrs: Dictionary; /** - * A dictionary that contains the default component modifiers + * A dictionary containing the default component modifiers */ mods: Dictionary; /** - * A dictionary that contains the accessor methods of the component that support caching or watching + * A dictionary containing the accessor methods of the component that support caching or watching */ computed: Dictionary>>; /** - * A dictionary that contains the component methods + * A dictionary containing the component methods */ methods: Dictionary; /** - * A dictionary that contains the available component directives + * A dictionary containing the available component directives */ directives?: Dictionary; /** - * A dictionary that contains the available local components + * A dictionary containing the available local components */ components?: Dictionary; diff --git a/src/core/component/queue-emitter/index.ts b/src/core/component/queue-emitter/index.ts index 73b628479a..c9bd3c29e9 100644 --- a/src/core/component/queue-emitter/index.ts +++ b/src/core/component/queue-emitter/index.ts @@ -55,15 +55,13 @@ export default class QueueEmitter { * @param event */ emit(event: string): CanPromise { - const - queue = this.listeners[event]; + const queue = this.listeners[event]; if (queue == null) { return; } - const - tasks: Array> = []; + const tasks: Array> = []; queue.forEach((el) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition @@ -99,8 +97,7 @@ export default class QueueEmitter { const tasks: Array> = []; queue.forEach((el) => { - const - task = el(); + const task = el(); if (Object.isPromise(task)) { tasks.push(task); diff --git a/src/core/component/reflect/interface.ts b/src/core/component/reflect/interface.ts index 95fafa203e..d48ecef0cb 100644 --- a/src/core/component/reflect/interface.ts +++ b/src/core/component/reflect/interface.ts @@ -9,8 +9,9 @@ import type { ComponentConstructor, - ComponentInterface, ComponentOptions, + + ComponentInterface, ComponentMeta } from 'core/component/interface'; @@ -48,7 +49,7 @@ export interface ComponentConstructorInfo { constructor: ComponentConstructor; /** - * A dictionary that contains the parameters provided to the `@component` decorator for the component + * A dictionary containing the parameters provided to the `@component` decorator for the component */ params: ComponentOptions; diff --git a/src/core/component/reflect/property.ts b/src/core/component/reflect/property.ts index 3cfbc7e949..4b479ab9dc 100644 --- a/src/core/component/reflect/property.ts +++ b/src/core/component/reflect/property.ts @@ -226,8 +226,7 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr if (accessorType != null) { if ((computedFields[name] ?? accessors[name])!.watchable) { - let - ctxPath: ObjectPropertyPath; + let ctxPath: ObjectPropertyPath; if (chunks != null) { ctxPath = chunks.slice(0, rootI + 1); diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index b766312c48..3c4d950b30 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -168,7 +168,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // } // // To address this issue, we can check if the handler requires a second argument by using the length property. - // If the second argument is needed, we can clone the old value and store it within a closure. + // If the second argument is necessary, we can clone the old value and store it within a closure. // // This covers the situations where we need to retain the old value // (a property watcher with a handler length greater than one), diff --git a/src/core/page-meta-data/elements/link/index.ts b/src/core/page-meta-data/elements/link/index.ts index 92aea54af0..24a4012afe 100644 --- a/src/core/page-meta-data/elements/link/index.ts +++ b/src/core/page-meta-data/elements/link/index.ts @@ -43,8 +43,11 @@ export class Link extends AbstractElement { this.attrs.type = value; } - protected override tag!: 'link'; - protected override attrs!: LinkAttributes; + /** @inheritDoc */ + declare protected tag: 'link'; + + /** @inheritDoc */ + declare protected attrs: LinkAttributes; constructor(engine: Engine, attrs: LinkAttributes) { super(engine, 'link', attrs); diff --git a/src/core/page-meta-data/elements/meta/index.ts b/src/core/page-meta-data/elements/meta/index.ts index a18708e542..3a20b844d9 100644 --- a/src/core/page-meta-data/elements/meta/index.ts +++ b/src/core/page-meta-data/elements/meta/index.ts @@ -33,8 +33,11 @@ export class Meta extends AbstractElement { this.attrs.name = value; } - protected override tag!: 'meta'; - protected override attrs!: MetaAttributes; + /** @inheritDoc */ + declare protected tag: 'meta'; + + /** @inheritDoc */ + declare protected attrs: MetaAttributes; constructor(engine: Engine, attrs: MetaAttributes) { super(engine, 'meta', attrs); diff --git a/src/core/page-meta-data/elements/title/index.ts b/src/core/page-meta-data/elements/title/index.ts index ee3f439b44..76bf20b977 100644 --- a/src/core/page-meta-data/elements/title/index.ts +++ b/src/core/page-meta-data/elements/title/index.ts @@ -24,8 +24,11 @@ export class Title extends AbstractElement { this.attrs.text = value; } - protected override tag!: 'title'; - protected override attrs!: TitleAttributes; + /** @inheritDoc */ + declare protected tag: 'title'; + + /** @inheritDoc */ + declare protected attrs: TitleAttributes; constructor(engine: Engine, attrs: TitleAttributes) { super(engine, 'title', attrs); From 19c468edd1540db3ebf49b5ca32095fb03482ed8 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 17:27:06 +0300 Subject: [PATCH 032/334] docs: improved doc --- src/components/base/b-bottom-slide/README.md | 9 ++--- src/components/base/b-list/README.md | 21 ++++------- src/components/base/b-slider/README.md | 6 ++-- src/components/base/b-tree/README.md | 9 ++--- src/components/form/b-select/README.md | 12 +++---- src/components/super/i-block/base/README.md | 20 +++++------ .../super/i-block/decorators/README.md | 2 +- src/components/super/i-block/event/README.md | 8 ++--- src/components/super/i-block/mods/README.md | 26 +++++++------- .../super/i-block/providers/README.md | 6 ++-- src/components/super/i-block/state/README.md | 30 ++++++++-------- src/components/traits/i-access/README.md | 4 +-- .../traits/i-data-provider/README.md | 2 +- src/components/traits/i-open-toggle/README.md | 2 +- src/components/traits/i-open/README.md | 4 +-- src/components/traits/i-progress/README.md | 2 +- src/components/traits/i-visible/README.md | 2 +- src/core/component/field/README.md | 2 +- src/core/component/hook/README.md | 2 +- src/core/component/meta/README.md | 36 +++++++++---------- src/core/component/queue-emitter/README.md | 4 +-- 21 files changed, 95 insertions(+), 114 deletions(-) diff --git a/src/components/base/b-bottom-slide/README.md b/src/components/base/b-bottom-slide/README.md index 7517f05f72..3f90d1687e 100644 --- a/src/components/base/b-bottom-slide/README.md +++ b/src/components/base/b-bottom-slide/README.md @@ -97,8 +97,7 @@ __b-modal.ts__ ```typescript @component() export default class bModal extends iBlock { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iBlock['$refs'] & { bottomSlide: bBottomSlide; }; @@ -135,8 +134,7 @@ __p-root.ts__ ```typescript @component({root: true}) export default class pV4ComponentsDemo extends iStaticPage { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iStaticPage['$refs'] & { modal: bModal; }; @@ -188,8 +186,7 @@ export default class bModal extends iBlock { @field() text?: string; - /** @override */ - protected $refs!: { + declare protected readonly $refs: iBlock['$refs'] & { bottomSlide: HTMLElement; }; diff --git a/src/components/base/b-list/README.md b/src/components/base/b-list/README.md index 35ad0621dc..e1d4b731f7 100644 --- a/src/components/base/b-list/README.md +++ b/src/components/base/b-list/README.md @@ -57,11 +57,9 @@ import bList, { component } from 'components/super/b-list/b-list'; @component() export default class MyList extends bList { - /** @override */ - readonly ActiveProp!: CanIter; + declare readonly ActiveProp: CanIter; - /** @override */ - readonly Active!: number | Set; + declare readonly Active: number | Set; } ``` @@ -72,8 +70,7 @@ import bList, { component } from 'components/super/b-list/b-list'; @component() export default class MyList extends bList { - /** @override */ - readonly Item!: MyItem; + declare readonly Item: MyItem; } ``` @@ -258,8 +255,7 @@ Returns true if the specified value is active. ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { list: bList }; @@ -277,8 +273,7 @@ If the component is switched to the `multiple` mode, the method can take an iter ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { list: bList }; @@ -295,8 +290,7 @@ If the component is switched to the `multiple` mode, the method can take an iter ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { list: bList }; @@ -313,8 +307,7 @@ The methods return a new active component item(s). ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { list: bList }; diff --git a/src/components/base/b-slider/README.md b/src/components/base/b-slider/README.md index 7ba7edd5a8..be5ff0a48f 100644 --- a/src/components/base/b-slider/README.md +++ b/src/components/base/b-slider/README.md @@ -228,8 +228,7 @@ Switches to the specified slide by an index. ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { slider: bSlider }; @@ -245,8 +244,7 @@ Moves to the next or previous slide. ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { slider: bSlider }; diff --git a/src/components/base/b-tree/README.md b/src/components/base/b-tree/README.md index 6ac2a64006..178a509409 100644 --- a/src/components/base/b-tree/README.md +++ b/src/components/base/b-tree/README.md @@ -36,11 +36,9 @@ import bTree, { component } from 'components/super/b-tree/b-tree'; @component() export default class MyTree extends bTree { - /** @override */ - readonly ActiveProp!: CanIter; + declare readonly ActiveProp: CanIter; - /** @override */ - readonly Active!: number | Set; + declare readonly Active: number | Set; } ``` @@ -51,8 +49,7 @@ import bTree, { component } from 'components/super/b-tree/b-tree'; @component() export default class MyTree extends bTree { - /** @override */ - readonly Item!: MyItem; + declare readonly Item: MyItem; } ``` diff --git a/src/components/form/b-select/README.md b/src/components/form/b-select/README.md index 5902935b18..4fe0f94b58 100644 --- a/src/components/form/b-select/README.md +++ b/src/components/form/b-select/README.md @@ -277,8 +277,7 @@ Returns true if the specified value is selected. ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { select: bSelect }; @@ -295,8 +294,7 @@ Selects an item with the specified value. If the component is in `multiple` mode ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { select: bSelect }; @@ -312,8 +310,7 @@ Removes the selection from an item with the specified value. If the component is ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { select: bSelect }; @@ -329,8 +326,7 @@ Toggles the selection of an item with the specified value. The method returns th ```typescript class Test extends iData { - /** @override */ - protected $refs!: { + declare protected readonly $refs: iData['$refs'] & { select: bSelect }; diff --git a/src/components/super/i-block/base/README.md b/src/components/super/i-block/base/README.md index 5e1787c94f..5ac4fae2d5 100644 --- a/src/components/super/i-block/base/README.md +++ b/src/components/super/i-block/base/README.md @@ -1,10 +1,10 @@ # components/super/i-block/base -This module offers a unified API for working with components +This module offers a unified API for working with components. ## API -### Associated types +### Associated Types The class declares two associated types, **Root** and **Component**, which are used to specify the types of components. @@ -18,7 +18,7 @@ import type iStaticPage from 'components/super/i-static-page/i-static-page'; @component() export default class bExample extends iBlock { - override readonly Root!: iStaticPage; + declare readonly Root: iStaticPage; } ``` @@ -31,7 +31,7 @@ import iBlock, { component } from 'components/super/i-block/i-block'; @component() export default class bExample extends iBlock { - override readonly Component!: iBlock; + declare readonly Component: iBlock; } ``` @@ -141,7 +141,7 @@ A link to the root component. #### rootAttrs -A dictionary with additional attributes for the component's root element. +A dictionary containing additional attributes for the component's root element. #### t @@ -172,11 +172,11 @@ export default class bExample extends iBlock { #### $refs -A dictionary with references to component elements that have the "ref" attribute. +A dictionary containing references to component elements that have the `ref` attribute. #### $slots -A dictionary with available render slots. +A dictionary containing available render slots. #### $root @@ -277,7 +277,7 @@ as it automatically synchronizes with the `keep-alive` mode or a specific compon #### watch -Sets a watcher to the component/object property or event by the specified path. +Sets a watcher to the component/object property or event at the specified path. When you observe changes to certain properties, the event handler function can accept a second argument that references the old value of the property. @@ -339,8 +339,8 @@ An empty reference '' is a reference to the component itself. Also, if you are listening to an event, you can control when to start listening to the event by using special characters at the beginning of the path string: -1. `'!'` - start listening to an event on the "beforeCreate" hook, e.g.: `'!rootEmitter:reset'`; -2. `'?'` - start listening to an event on the "mounted" hook, e.g.: `'?$el:click'`. +1. `'!'` - start listening to an event on the "beforeCreate" hook, e.g., `'!rootEmitter:reset'`; +2. `'?'` - start listening to an event on the "mounted" hook, e.g., `'?$el:click'`. By default, all events start listening on the "created" hook. diff --git a/src/components/super/i-block/decorators/README.md b/src/components/super/i-block/decorators/README.md index 98bb13ed1c..e4444da277 100644 --- a/src/components/super/i-block/decorators/README.md +++ b/src/components/super/i-block/decorators/README.md @@ -2,7 +2,7 @@ This module re-exports the base decorators from `core/component/decorators` and also provides additional decorators. -## Re-exported decorators +## Re-exported Decorators * `@component` to register a new component; * `@prop` to declare a component input property (aka "prop"); diff --git a/src/components/super/i-block/event/README.md b/src/components/super/i-block/event/README.md index 39341682b5..adab86caf6 100644 --- a/src/components/super/i-block/event/README.md +++ b/src/components/super/i-block/event/README.md @@ -252,7 +252,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - override readonly SelfEmitter!: InferComponentEvents; @@ -279,7 +279,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - override readonly SelfEmitter!: InferComponentEvents; @@ -681,7 +681,7 @@ import iBlock, { component, InferEvents } from 'components/super/i-block/i-block @component() export default class bExample extends iBlock { - override readonly LocalEmitter!: InferEvents<[ + declare readonly LocalEmitter: InferEvents<[ ['myEvent', {data: string}] ], iBlock['LocalEmitter']>; @@ -699,7 +699,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - override readonly LocalEmitter!: InferEvents<[ + declare readonly LocalEmitter: InferEvents<[ ['myEvent', {data: string}] ], iBlock['LocalEmitter']>; diff --git a/src/components/super/i-block/mods/README.md b/src/components/super/i-block/mods/README.md index c06ddc8199..48590ec463 100644 --- a/src/components/super/i-block/mods/README.md +++ b/src/components/super/i-block/mods/README.md @@ -1,10 +1,10 @@ # components/super/i-block/mods -This module provides an API for working with components using -the [BEM](https://en.bem.info/methodology/quick-start/) methodology. +This module provides an API +for working with components using the [BEM](https://en.bem.info/methodology/quick-start/) methodology. The implementation is delegated to the [[Block]] friendly class. -## Basic concepts +## Basic Concepts The BEM methodology describes how to apply a component-based approach to CSS when defining a widget. The methodology defines three main entities: block, element, and modifier. @@ -14,15 +14,15 @@ block modifiers are its inputs, which also have a convention for adding necessar Additionally, elements can also have their own modifiers, which are conveniently applied at a micro level in the component's markup. -### How to declare component props as modifiers? +### Declaring Component Props as Modifiers To declare modifiers for a component, you need to use the static property `mods`. -Simply pass it a dictionary where the keys are the modifier names and the values are lists representing their values. +Pass it a dictionary where the keys are the modifier names and the values are lists representing their values. The modifier values are always converted to strings. However, when describing them, you can use numbers and boolean values. Additionally, all modifier names and values are forcibly normalized to kebab case, so you can use any style that suits you. -To assign any of the default values, simply wrap it in another array. +To assign any of the default values, wrap it in another array. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; @@ -126,7 +126,7 @@ class Children extends Parent { } ``` -### How to pass modifiers when creating a component? +### How to Pass Modifiers When Creating a Component? Any component modifier can be set externally when the component is created, just like a regular prop. @@ -152,13 +152,13 @@ Of course, you can combine both methods. < b-example :theme = 'dark' | :mods = mods ``` -#### Automatically inherited modifiers +#### Automatically Inherited Modifiers All V4Fire components have a getter called `sharedMods`, which returns a dictionary of modifiers that can be passed to any child components. If you don't explicitly pass the `mods` prop when creating a component, the `sharedMods` getter will be automatically passed to it. -This is very useful when you need to propagate certain modifiers to all nested components. +This is handy when you need to propagate certain modifiers to all nested components. By default, the getter returns a dictionary with only the `theme` modifier or undefined if it's not specified. ``` @@ -192,7 +192,7 @@ import iBlock, { component } from 'components/super/i-block/i-block'; class bExample extends iBlock {} ``` -### How to get component modifier value? +### Getting a Component's Modifier Value All component's applied modifiers are stored in the `mods` read-only property. Therefore, to get the value of any modifier, simply access the desired key. @@ -258,7 +258,7 @@ If you want to use modifiers within a component template, then use the `m` gette ... ``` -### How to set a new component's modifier value? +### Setting a New Component's Modifier Value To set a new modifier value or remove an existing one, you can use the `setMod` and `removeMod` methods. @@ -284,7 +284,7 @@ class bExample extends iBlock { } ``` -#### Modifier change events +#### Modifier Change Events Whenever the value of any modifier changes, the component will emit a series of events that can be listened to both inside and outside the component. @@ -320,7 +320,7 @@ class bExample extends iBlock { } ``` -##### Local events +##### Local Events All set and remove operations for modifiers also trigger local component events that cannot be handled from the outside. Since all local component events can be listened to using placeholders, diff --git a/src/components/super/i-block/providers/README.md b/src/components/super/i-block/providers/README.md index 6fddcc49e7..56f9ba1cd9 100644 --- a/src/components/super/i-block/providers/README.md +++ b/src/components/super/i-block/providers/README.md @@ -2,7 +2,7 @@ This module provides an API for initializing and loading external data to a component. -## How does a component load its data? +## How a Component Loads Its Data When a component is created, it calls its `initLoad` method. This method, depending on the component parameters, can either immediately switch it to `ready` (nothing needs to be loaded), or initialize the loading of resources: @@ -15,7 +15,7 @@ initialize re-rendering (if necessary). #### [dependenciesProp] -An iterable with additional dependencies to load when the component is initializing. +An iterable object with additional component dependencies for initialization. ```typescript import iBlock, { component, Module } from 'components/super/i-block/i-block'; @@ -45,7 +45,7 @@ By default, this prop is automatically calculated based on component dependencie #### dependencies -A list of additional dependencies to load when the component is initializing. +A list of additional dependencies to load during the component's initialization. The parameter is tied with the `dependenciesProp` prop. #### dontWaitRemoteProviders diff --git a/src/components/super/i-block/state/README.md b/src/components/super/i-block/state/README.md index ed065de6d3..691eaa2b0a 100644 --- a/src/components/super/i-block/state/README.md +++ b/src/components/super/i-block/state/README.md @@ -2,7 +2,7 @@ This module provides an API for convenient work with component states. -## Component lifecycle +## Component Lifecycle Each V4Fire component instance undergoes a series of initialization steps when it's created; for instance, it needs to set up data observation, compile the template, mount the instance to the DOM, @@ -10,7 +10,7 @@ and update the DOM when data changes. Throughout the process, it also initiates functions called lifecycle hooks, allowing users to incorporate their own code at specific stages. -### Supported hooks +### Supported Hooks V4Fire components follow a standard life cycle: the component is created, the component is mounted to the DOM, the component is unmounted from the DOM, and so on. @@ -54,7 +54,7 @@ protected initBaseAPI() { In other words, before `beforeCreate`, there's a special method that is invoked to explicitly set the most essential API, which the component should always possess. -There aren't many methods that can be used before the `created` hook, +There are few methods that can be used before the `created` hook, and usually, all of them are registered in `iBlock.initBaseAPI`. However, if your component has a new method that needs to be used in this manner, the `initBaseAPI` method can always be overridden. @@ -64,7 +64,7 @@ the `initBaseAPI` method can always be overridden. Often, it is crucial to perform some modifications to watchable fields (like normalization) before creating a component, because once created, any change to these fields can trigger re-rendering and potentially be detrimental to performance. We have links, initializers, and API to manage the order of initialization, but in case we need to access the entire -watchable store and modify it in a complex manner, the `beforeDataCreate` hook comes to the rescue. +watchable store and modify it complexly, the `beforeDataCreate` hook comes to the rescue. This hook is exactly triggered when all observable properties have been formulated but are not yet linked to the component. Therefore, we can safely alter them without worrying about repercussions. @@ -97,7 +97,7 @@ so there is no need for special methods or hooks to access them. Typically, it's better to use link mechanisms for establishing relationships during initialization and normalization. However, `beforeDataCreate` can still prove to be quite useful. -### Hook change events +### Hook Change Events Every time a component hook value changes, the component triggers a series of events that can be listened to both internally and externally to the component. @@ -107,7 +107,7 @@ the component triggers a series of events that can be listened to both internall | `hook:$name` | The component switched to a hook named $name | The new hook value; The previous hook value | `string`; `string` | | `hookChange` | The component switched to a new hook | The new hook value; The previous hook value | `string`; `string` | -### Registering lifecycle hooks +### Registering Lifecycle Hooks To bind a method to a specific hook, there are three ways: @@ -165,7 +165,7 @@ To bind a method to a specific hook, there are three ways: } ``` -### Component hook accessor +### Component Hook Accessor All V4Fire components have a hook accessor that indicates the current hook of the component. @@ -182,7 +182,7 @@ class bExample extends iBlock { } ``` -### Hook handler execution order +### Hook Handler Execution Order All hook handlers are executed in a queue: those added through the decorator are executed first (in the order of addition), followed by the execution of the associated methods (if any). @@ -216,13 +216,13 @@ export default class bExample extends iBlock { } ``` -### Asynchronous handlers +### Asynchronous Handlers Certain hooks support asynchronous handlers: `mounted`, `updated`, `destroyed`, `renderTriggered`, and `errorCaptured`. That is, if one of the hook handlers returns a Promise, then the rest will wait for its resolution to maintain the initialization order. -## Component status +## Component Status V4Fire provides a special status for components that reflects their state: whether the component is loading, ready, and so on. @@ -253,7 +253,7 @@ This property can assume the following values: 6. `destroyed` - the component has been destroyed: this status might coincide with certain component hooks such as `beforeDestroy` or `destroyed`. -### Component status change events +### Component Status Change Events Each time a component status value changes, the component emits a series of events that can be listened to both internally and externally to the component. @@ -280,7 +280,7 @@ export default class bExample extends iBlock { } ``` -### Component status accessor +### Component Status Accessor All V4Fire components have a status accessor that indicates the current status of the component. @@ -295,7 +295,7 @@ class bExample extends iBlock { } ``` -### The `@wait` decorator +### The `@wait` Decorator This decorator addresses the issue of invoking component methods when the component is not yet ready for it. Refer to the documentation for the `components/super/i-block/decorators` module. @@ -332,7 +332,7 @@ class bExample extends iBlock { } ``` -## Synchronizing component state with external sources +## Syncing Component State with External Sources Any component can bind its state to the state of another external module. For instance, a component may store some of its properties in local storage. @@ -341,7 +341,7 @@ and conversely, when the component initializes, we must read its value from the This is precisely what this module does — it provides a set of APIs to synchronize external states with the component state. -### How does synchronization work? +### How Synchronization Works Synchronization operates using two-way connector methods. For instance, when a component is initializing, it invokes the special `syncStorageState` method, diff --git a/src/components/traits/i-access/README.md b/src/components/traits/i-access/README.md index be16df8bda..691cd1369e 100644 --- a/src/components/traits/i-access/README.md +++ b/src/components/traits/i-access/README.md @@ -26,7 +26,7 @@ or disabling. class bButton extends iBlock implements iAccess { static override readonly mods: ModsDecl = { ...iAccess.mods - } + }; protected override initModEvents(): void { super.initModEvents(); @@ -53,7 +53,7 @@ import iBlock, { component } from 'components/super/i-block/i-block'; export default class bButton extends iBlock implements iAccess { static override readonly mods: ModsDecl = { ...iAccess.mods - } + }; } ``` diff --git a/src/components/traits/i-data-provider/README.md b/src/components/traits/i-data-provider/README.md index dd8bbd69d4..e2781e618a 100644 --- a/src/components/traits/i-data-provider/README.md +++ b/src/components/traits/i-data-provider/README.md @@ -24,7 +24,7 @@ import iDataProvider from 'components/traits/i-data-provider/i-data-provider'; export default class bButton implements iDataProvider { static override readonly mods: ModsDecl = { ...iDataProvider.mods - } + }; } ``` diff --git a/src/components/traits/i-open-toggle/README.md b/src/components/traits/i-open-toggle/README.md index 50b717d1c1..8b6367c0a0 100644 --- a/src/components/traits/i-open-toggle/README.md +++ b/src/components/traits/i-open-toggle/README.md @@ -25,7 +25,7 @@ This module provides a trait for a component that extends the "opening/closing" class bButton extends iBlock implements iOpenToggle { static override readonly mods: ModsDecl = { ...iOpenToggle.mods - } + }; protected override initModEvents(): void { super.initModEvents(); diff --git a/src/components/traits/i-open/README.md b/src/components/traits/i-open/README.md index 39721234a9..f90b6af1f7 100644 --- a/src/components/traits/i-open/README.md +++ b/src/components/traits/i-open/README.md @@ -23,7 +23,7 @@ This module provides a trait for a component that needs to implement the "openin class bButton extends iBlock implements iOpen { static override readonly mods: ModsDecl = { ...iOpen.mods - } + }; protected override initModEvents(): void { super.initModEvents(); @@ -50,7 +50,7 @@ import iBlock, { component } from 'components/super/i-block/i-block'; export default class bButton implements iOpen { static override readonly mods: ModsDecl = { ...iOpen.mods - } + }; } ``` diff --git a/src/components/traits/i-progress/README.md b/src/components/traits/i-progress/README.md index a091df9cee..8b34162c9c 100644 --- a/src/components/traits/i-progress/README.md +++ b/src/components/traits/i-progress/README.md @@ -24,7 +24,7 @@ import iBlock, { component } from 'components/super/i-block/i-block'; export default class bButton extends iBlock implements iProgress { static override readonly mods: ModsDecl = { ...iProgress.mods - } + }; } ``` diff --git a/src/components/traits/i-visible/README.md b/src/components/traits/i-visible/README.md index 90ec992402..7774e6a78a 100644 --- a/src/components/traits/i-visible/README.md +++ b/src/components/traits/i-visible/README.md @@ -26,7 +26,7 @@ import iBlock, { component } from 'components/super/i-block/i-block'; export default class bButton extends iBlock implements iVisible { static override readonly mods: ModsDecl = { ...iVisible.mods - } + }; } ``` diff --git a/src/core/component/field/README.md b/src/core/component/field/README.md index b4bfb40f57..54acee6fa2 100644 --- a/src/core/component/field/README.md +++ b/src/core/component/field/README.md @@ -20,5 +20,5 @@ developers can design and optimize their components for optimal performance and ### initFields Initializes all fields of a given component instance. -This function returns a dictionary that contains the names of the initialized fields as keys, +This function returns A dictionary containing the names of the initialized fields as keys, with their corresponding initialized values as values. diff --git a/src/core/component/hook/README.md b/src/core/component/hook/README.md index 5d5e70e011..4068ee5f66 100644 --- a/src/core/component/hook/README.md +++ b/src/core/component/hook/README.md @@ -7,7 +7,7 @@ This module provides an API to manage component hooks. ### runHook Runs a hook on the specified component instance. -The function returns a promise that is resolved when all hook handlers are executed. +The function returns a promise resolved when all hook handlers are executed. ```js runHook('beforeCreate', component).then(() => console.log('Done!')); diff --git a/src/core/component/meta/README.md b/src/core/component/meta/README.md index f4f1d0cf49..6085170a51 100644 --- a/src/core/component/meta/README.md +++ b/src/core/component/meta/README.md @@ -30,7 +30,7 @@ export interface ComponentMeta { instance: Dictionary; /** - * A dictionary that contains the parameters provided to the `@component` decorator for the component + * A dictionary containing the parameters provided to the `@component` decorator for the component */ params: ComponentOptions; @@ -40,12 +40,12 @@ export interface ComponentMeta { parentMeta?: ComponentMeta; /** - * A dictionary that contains the input properties (props) for the component + * A dictionary containing the input properties (props) for the component */ props: Dictionary; /** - * A dictionary that contains the available component modifiers. + * A dictionary containing the available component modifiers. * Modifiers are a way to alter the behavior or appearance of a component without changing its underlying * functionality. * They can be used to customize components for specific use cases, or to extend their capabilities. @@ -54,12 +54,12 @@ export interface ComponentMeta { mods: ModsDecl; /** - * A dictionary that contains the component fields that can trigger a re-rendering of the component + * A dictionary containing the component fields that can trigger a re-rendering of the component */ fields: Dictionary; /** - * A dictionary that contains the component fields that do not cause a re-rendering of the component when they change. + * A dictionary containing the component fields that do not cause a re-rendering of the component when they change. * These fields are typically used for internal bookkeeping or for caching computed values, * and do not affect the visual representation of the component. * Examples include variables used for storing data or for tracking the component's internal state, @@ -70,24 +70,24 @@ export interface ComponentMeta { systemFields: Dictionary; /** - * A dictionary that contains the component fields that have a "Store" postfix in their name + * A dictionary containing the component fields that have a "Store" postfix in their name */ tiedFields: Dictionary; /** - * A dictionary that contains the accessor methods of the component that support caching or watching + * A dictionary containing the accessor methods of the component that support caching or watching */ computedFields: Dictionary; /** - * A dictionary that contains the simple component accessors, + * A dictionary containing the simple component accessors, * which are typically used for retrieving or modifying the value of a non-reactive property * that does not require caching or watching */ accessors: Dictionary; /** - * A dictionary that contains the component methods + * A dictionary containing the component methods */ methods: Dictionary; @@ -97,18 +97,18 @@ export interface ComponentMeta { watchers: Dictionary; /** - * A dictionary that contains the component dependencies to watch to invalidate the cache of computed fields + * A dictionary containing the component dependencies to watch to invalidate the cache of computed fields */ watchDependencies: ComponentWatchDependencies; /** - * A dictionary that contains the component prop dependencies to watch + * A dictionary containing the component prop dependencies to watch * to invalidate the cache of computed fields */ watchPropDependencies: ComponentWatchPropDependencies; /** - * A dictionary that contains the component hook listeners, + * A dictionary containing the component hook listeners, * which are essentially functions that are executed at specific stages in the V4Fire component's lifecycle */ hooks: ComponentHooks; @@ -124,7 +124,7 @@ export interface ComponentMeta { name: string; /** - * A dictionary that contains the input properties (props) for the component + * A dictionary containing the input properties (props) for the component */ props: Dictionary; @@ -135,27 +135,27 @@ export interface ComponentMeta { attrs: Dictionary; /** - * A dictionary that contains the default component modifiers + * A dictionary containing the default component modifiers */ mods: Dictionary; /** - * A dictionary that contains the accessor methods of the component that support caching or watching + * A dictionary containing the accessor methods of the component that support caching or watching */ computed: Dictionary>>; /** - * A dictionary that contains the component methods + * A dictionary containing the component methods */ methods: Dictionary; /** - * A dictionary that contains the available component directives + * A dictionary containing the available component directives */ directives?: Dictionary; /** - * A dictionary that contains the available local components + * A dictionary containing the available local components */ components?: Dictionary; diff --git a/src/core/component/queue-emitter/README.md b/src/core/component/queue-emitter/README.md index 967b0538c7..8fc8e0f4aa 100644 --- a/src/core/component/queue-emitter/README.md +++ b/src/core/component/queue-emitter/README.md @@ -1,8 +1,8 @@ # core/component/queue-emitter This module provides a class for creating an EventEmitter with support for handler queue ordering. -For example, -it's possible to declare that a handler will only be executed after multiple specified events have been triggered. +For example, it's possible to declare that a handler will only be executed after multiple specified +events have been triggered. ## Usage From 256355b5fddc0ad24c09ebcd0ab0af4963d6b649 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 17:28:43 +0300 Subject: [PATCH 033/334] docs: improved doc --- src/components/base/b-virtual-scroll-new/README.md | 2 +- src/components/friends/storage/README.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/README.md b/src/components/base/b-virtual-scroll-new/README.md index 0d447fccfd..cd6210d7ab 100644 --- a/src/components/base/b-virtual-scroll-new/README.md +++ b/src/components/base/b-virtual-scroll-new/README.md @@ -373,7 +373,7 @@ To retrieve the component's state, you can use a special method called `getVirtu ```typescript @component() class pPage extends iDynamicPage { - protected override readonly $refs!: { + declare protected readonly $refs: { scroll: bVirtualScrollNew; }; diff --git a/src/components/friends/storage/README.md b/src/components/friends/storage/README.md index 254062756f..e80cfef2be 100644 --- a/src/components/friends/storage/README.md +++ b/src/components/friends/storage/README.md @@ -55,9 +55,8 @@ import iBlock, { component, system } from 'components/super/i-block/i-block'; @component() export default class bExample extends iBlock { - /** @override */ @system((ctx) => new Storage(ctx, IDBEngine.asyncLocalStorage)) - protected readonly storage!: Storage; + protected override readonly storage!: Storage; } ``` From ef456cc9990f45e1145377a677db5b73b6bda29c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 17:32:26 +0300 Subject: [PATCH 034/334] chore: fixed TS errors --- .../base/b-bottom-slide/b-bottom-slide.ts | 12 +++---- .../base/b-bottom-slide/modules/animation.ts | 3 +- .../base/b-bottom-slide/modules/geometry.ts | 3 +- .../base/b-bottom-slide/modules/overlay.ts | 3 +- .../b-bottom-slide/modules/swipe-control.ts | 3 +- .../base/b-dynamic-page/b-dynamic-page.ts | 36 +++++++++---------- .../base/b-dynamic-page/interface.ts | 4 +-- .../b-scroll-element-dummy.ts | 3 +- .../base/b-virtual-scroll/b-virtual-scroll.ts | 9 +++-- src/components/friends/field/delete.ts | 2 +- src/components/friends/field/get.ts | 2 +- src/components/friends/field/set.ts | 2 +- .../p-v4-components-demo.ts | 1 + 13 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/components/base/b-bottom-slide/b-bottom-slide.ts b/src/components/base/b-bottom-slide/b-bottom-slide.ts index 829dcfcc08..61edeaa4db 100644 --- a/src/components/base/b-bottom-slide/b-bottom-slide.ts +++ b/src/components/base/b-bottom-slide/b-bottom-slide.ts @@ -43,14 +43,13 @@ const $$ = symbolGenerator(); Block.addToPrototype({getFullElementName}); -interface bBottomSlide extends - Trait, - Trait {} +interface bBottomSlide extends Trait, Trait {} @component() @derive(iLockPageScroll, iOpen) class bBottomSlide extends iBottomSlideProps implements iLockPageScroll, iOpen, iVisible, iHistory { - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } @@ -98,7 +97,7 @@ class bBottomSlide extends iBottomSlideProps implements iLockPageScroll, iOpen, } /** {@link iHistory.history} */ - @system((ctx) => new History(ctx)) + @system((o) => new History(Object.cast(o))) readonly history!: History; static override readonly mods: ModsDecl = { @@ -121,7 +120,8 @@ class bBottomSlide extends iBottomSlideProps implements iLockPageScroll, iOpen, ] }; - protected override readonly $refs!: iBlock['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iBlock['$refs'] & { view: HTMLElement; window: HTMLElement; header: HTMLElement; diff --git a/src/components/base/b-bottom-slide/modules/animation.ts b/src/components/base/b-bottom-slide/modules/animation.ts index b956ce93f7..466bdc79d1 100644 --- a/src/components/base/b-bottom-slide/modules/animation.ts +++ b/src/components/base/b-bottom-slide/modules/animation.ts @@ -16,7 +16,8 @@ const $$ = symbolGenerator(); export default class Animation extends Friend { - override readonly C!: bBottomSlide; + /** @inheritDoc */ + declare readonly C: bBottomSlide; /** * True if all animations need to use `requestAnimationFrame` diff --git a/src/components/base/b-bottom-slide/modules/geometry.ts b/src/components/base/b-bottom-slide/modules/geometry.ts index 5797edc413..0ad6513616 100644 --- a/src/components/base/b-bottom-slide/modules/geometry.ts +++ b/src/components/base/b-bottom-slide/modules/geometry.ts @@ -16,7 +16,8 @@ const $$ = symbolGenerator(); export default class Geometry extends Friend { - override readonly C!: bBottomSlide; + /** @inheritDoc */ + declare readonly C: bBottomSlide; /** * Window height diff --git a/src/components/base/b-bottom-slide/modules/overlay.ts b/src/components/base/b-bottom-slide/modules/overlay.ts index 5a72e8661d..b2c7cdc39c 100644 --- a/src/components/base/b-bottom-slide/modules/overlay.ts +++ b/src/components/base/b-bottom-slide/modules/overlay.ts @@ -15,7 +15,8 @@ import type bBottomSlide from 'components/base/b-bottom-slide/b-bottom-slide'; const $$ = symbolGenerator(); export default class Overlay extends Friend { - override readonly C!: bBottomSlide; + /** @inheritDoc */ + declare readonly C: bBottomSlide; /** * Current value of the overlay opacity diff --git a/src/components/base/b-bottom-slide/modules/swipe-control.ts b/src/components/base/b-bottom-slide/modules/swipe-control.ts index 4e60b721f8..73477664c7 100644 --- a/src/components/base/b-bottom-slide/modules/swipe-control.ts +++ b/src/components/base/b-bottom-slide/modules/swipe-control.ts @@ -12,7 +12,8 @@ import type bBottomSlide from 'components/base/b-bottom-slide/b-bottom-slide'; import type { Direction } from 'components/base/b-bottom-slide/interface'; export default class SwipeControl extends Friend { - override readonly C!: bBottomSlide; + /** @inheritDoc */ + declare readonly C: bBottomSlide; /** * Current cursor direction diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index 22446fd38d..e1e46a7669 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -209,13 +209,15 @@ export default class bDynamicPage extends iDynamicPage { } } - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } protected override readonly componentStatusStore: ComponentStatus = 'ready'; - protected override readonly $refs!: iDynamicPage['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iDynamicPage['$refs'] & { component?: iDynamicPage[]; }; @@ -343,11 +345,9 @@ export default class bDynamicPage extends iDynamicPage { return true; } - const { - unsafe, - route, - r - } = this; + const that = this; + + const {route, r} = this; return new SyncPromise((resolve) => { this.onPageChange = onPageChange(resolve, this.route); @@ -358,13 +358,13 @@ export default class bDynamicPage extends iDynamicPage { currentRoute: typeof route ): AnyFunction { return (newPage: CanUndef, currentPage: CanUndef) => { - unsafe.pageTakenFromCache = false; + that.pageTakenFromCache = false; - const componentRef = unsafe.$refs[unsafe.$resolveRef('component')]; + const componentRef = that.$refs[that.$resolveRef('component')]; componentRef?.pop(); const - currentPageEl = unsafe.block?.element('component'), + currentPageEl = that.block?.element('component'), currentPageComponent = currentPageEl?.component?.unsafe; if (currentPageEl != null) { @@ -372,7 +372,7 @@ export default class bDynamicPage extends iDynamicPage { if (currentPageComponent != null) { const - currentPageStrategy = unsafe.getKeepAliveStrategy(currentPage, currentRoute); + currentPageStrategy = that.getKeepAliveStrategy(currentPage, currentRoute); if (currentPageStrategy.isLoopback) { currentPageComponent.$destroy(); @@ -387,17 +387,17 @@ export default class bDynamicPage extends iDynamicPage { } const - newPageStrategy = unsafe.getKeepAliveStrategy(newPage), + newPageStrategy = that.getKeepAliveStrategy(newPage), pageElFromCache = newPageStrategy.get(); if (pageElFromCache == null) { const handler = () => { if (!newPageStrategy.isLoopback) { - return SyncPromise.resolve(unsafe.component).then((c) => c.activate(true)); + return SyncPromise.resolve(that.component).then((c) => c.activate(true)); } }; - unsafe.localEmitter.once('asyncRenderChunkComplete', handler, { + that.localEmitter.once('asyncRenderChunkComplete', handler, { label: $$.renderFilter }); @@ -408,15 +408,15 @@ export default class bDynamicPage extends iDynamicPage { if (pageComponentFromCache != null) { pageComponentFromCache.activate(); - unsafe.async.requestAnimationFrame(() => { + that.async.requestAnimationFrame(() => { restorePageElementsScroll(pageElFromCache); }, {label: $$.restorePageElementsScroll}); - unsafe.$el?.append(pageElFromCache); + that.$el?.append(pageElFromCache); pageComponentFromCache.emit('mounted', pageElFromCache); componentRef?.push(pageComponentFromCache); - unsafe.pageTakenFromCache = true; + that.pageTakenFromCache = true; } else { newPageStrategy.remove(); @@ -429,7 +429,7 @@ export default class bDynamicPage extends iDynamicPage { // However, we can't guarantee that the next `renderFilter` call will occur before `syncPageWatcher`. // If `syncPageWatcher` is called before the next `renderFilter`, it will execute // the `onPageChange` callback, which is why we must clean it up here. - unsafe.onPageChange = undefined; + that.onPageChange = undefined; resolve(true); }; diff --git a/src/components/base/b-dynamic-page/interface.ts b/src/components/base/b-dynamic-page/interface.ts index 400f73d2a0..06f576831d 100644 --- a/src/components/base/b-dynamic-page/interface.ts +++ b/src/components/base/b-dynamic-page/interface.ts @@ -66,10 +66,10 @@ export interface UnsafeBDynamicPage ext renderFilter: CTX['renderFilter']; // @ts-ignore (access) - getKeepAliveStrategy: CTX['getKeepAliveStrategy']; + addClearListenersToCache: CTX['addClearListenersToCache']; // @ts-ignore (access) - wrapCache: CTX['wrapCache']; + getKeepAliveStrategy: CTX['getKeepAliveStrategy']; } export interface OnBeforeSwitchPage { diff --git a/src/components/base/b-dynamic-page/test/b-scroll-element-dummy/b-scroll-element-dummy.ts b/src/components/base/b-dynamic-page/test/b-scroll-element-dummy/b-scroll-element-dummy.ts index 36ac739542..299db75c57 100644 --- a/src/components/base/b-dynamic-page/test/b-scroll-element-dummy/b-scroll-element-dummy.ts +++ b/src/components/base/b-dynamic-page/test/b-scroll-element-dummy/b-scroll-element-dummy.ts @@ -13,7 +13,8 @@ export * from 'components/dummies/b-dummy/b-dummy'; @component() export class bScrollElementDummy extends bDummy { - override readonly $refs!: bDummy['$refs'] & { + /** @inheritDoc */ + declare readonly $refs: bDummy['$refs'] & { scrollable: HTMLUListElement; }; diff --git a/src/components/base/b-virtual-scroll/b-virtual-scroll.ts b/src/components/base/b-virtual-scroll/b-virtual-scroll.ts index 33b02b3897..4c49fce984 100644 --- a/src/components/base/b-virtual-scroll/b-virtual-scroll.ts +++ b/src/components/base/b-virtual-scroll/b-virtual-scroll.ts @@ -86,7 +86,8 @@ export default class bVirtualScroll extends iData implements iItems { /** {@link iItems.Items} */ readonly Items!: Array; - override readonly DB!: RemoteData; + /** @inheritDoc */ + declare readonly DB: RemoteData; override readonly checkDBEquality: CheckDBEquality = false; @@ -185,7 +186,8 @@ export default class bVirtualScroll extends iData implements iItems { this.field.set('itemsStore', value); } - override get unsafe(): UnsafeGetter> { + // @ts-ignore (override) + override get unsafe(): UnsafeGetter> { return Object.cast(this); } @@ -251,7 +253,8 @@ export default class bVirtualScroll extends iData implements iItems { @system((o) => new ComponentRender(o)) protected componentRender!: ComponentRender; - protected override readonly $refs!: iData['$refs'] & { + /** @inheritDoc */ + declare protected readonly $refs: iData['$refs'] & { container: HTMLElement; loader?: HTMLElement; tombstones?: HTMLElement; diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index 4dada33f50..b36b8c53eb 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -107,7 +107,7 @@ export function deleteField( if (isComponent) { const - info = getPropertyInfo(path, ctx); + info = getPropertyInfo(path, this.component); const isReady = !ctx.lfc.isBeforeCreate(), diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index 78974d1cde..f1010a561f 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -96,7 +96,7 @@ export function getField( if (isComponent) { const - info = getPropertyInfo(path, ctx); + info = getPropertyInfo(path, this.component); ctx = Object.cast(info.ctx); res = ctx; diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index f6c2ffe595..e229b51497 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -105,7 +105,7 @@ export function setField( if (isComponent) { const - info = getPropertyInfo(path, ctx); + info = getPropertyInfo(path, this.component); ctx = Object.cast(info.ctx); ref = ctx; diff --git a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts index 1f7884f7b6..a6b858a092 100644 --- a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts +++ b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts @@ -29,6 +29,7 @@ export default class pV4ComponentsDemo extends iStaticPage { readonly selfDispatchingProp: boolean = false; @system((o) => o.sync.link()) + // @ts-ignore (override) override readonly selfDispatching!: boolean; /** From 5fd6b9098933e716a8df745837ec6803d492d196 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 17:38:37 +0300 Subject: [PATCH 035/334] chore: reverted --- .../test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts b/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts index ea29db3339..be80e44e37 100644 --- a/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts +++ b/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts @@ -17,8 +17,10 @@ export * from 'components/dummies/b-dummy/b-dummy'; Daemons.addToPrototype(DaemonsAPI); +interface bFriendsDaemonsDummy extends Dictionary {} + @component() -export default class bFriendsDaemonsDummy extends bDummy { +class bFriendsDaemonsDummy extends bDummy { // @ts-ignore (override) override get unsafe(): UnsafeGetter> { return Object.cast(this); @@ -91,3 +93,5 @@ export default class bFriendsDaemonsDummy extends bDummy { } }; } + +export default bFriendsDaemonsDummy; From 95dcb2648bcd755ac23bf0612795ef883b098084 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 17:50:31 +0300 Subject: [PATCH 036/334] fix: fixed invalid refactoring --- .../b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts | 2 +- src/components/super/i-page/i-page.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/super/i-block/test/b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts b/src/components/super/i-block/test/b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts index 21f5345d6f..487490b07f 100644 --- a/src/components/super/i-block/test/b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts +++ b/src/components/super/i-block/test/b-super-i-block-watch-dummy/b-super-i-block-watch-dummy.ts @@ -53,7 +53,7 @@ export default class bSuperIBlockWatchDummy extends iData { return this.r.isAuth; } - @computed({cache: 'forever', dependencies: ['complexObjStore']}) + @computed({cache: true, dependencies: ['complexObjStore']}) get cachedComplexObj(): Dictionary { return Object.fastClone(this.complexObjStore); } diff --git a/src/components/super/i-page/i-page.ts b/src/components/super/i-page/i-page.ts index af03912ee6..adce23223a 100644 --- a/src/components/super/i-page/i-page.ts +++ b/src/components/super/i-page/i-page.ts @@ -96,7 +96,7 @@ export default abstract class iPage extends iData implements iVisible { * * {@link iPage.scrollTo} */ - @computed({cache: 'forever'}) + @computed({cache: true}) get scrollToProxy(): this['scrollTo'] { return (...args) => { this.async.setImmediate(() => this.scrollTo(...args), { From 2fad82a01b0f60f69a0d3cd8c12ff549d1bf5bbd Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 18:11:40 +0300 Subject: [PATCH 037/334] fix: fixed after refactoring --- src/components/friends/field/delete.ts | 3 +-- src/components/friends/field/get.ts | 3 +-- src/components/friends/field/set.ts | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index b36b8c53eb..57b8b122a6 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -106,8 +106,7 @@ export function deleteField( chunks; if (isComponent) { - const - info = getPropertyInfo(path, this.component); + const info = getPropertyInfo(path, Object.cast(ctx)); const isReady = !ctx.lfc.isBeforeCreate(), diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index f1010a561f..bf1967e3c0 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -95,8 +95,7 @@ export function getField( chunks; if (isComponent) { - const - info = getPropertyInfo(path, this.component); + const info = getPropertyInfo(path, Object.cast(ctx)); ctx = Object.cast(info.ctx); res = ctx; diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index e229b51497..d07bd30f7f 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -104,8 +104,7 @@ export function setField( chunks; if (isComponent) { - const - info = getPropertyInfo(path, this.component); + const info = getPropertyInfo(path, Object.cast(ctx)); ctx = Object.cast(info.ctx); ref = ctx; From 52ed77eec1796f6912cc4a5785ee28124bca21f0 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 19:17:27 +0300 Subject: [PATCH 038/334] fix: fixed after refactoring --- src/core/component/field/init.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/component/field/init.ts b/src/core/component/field/init.ts index 6570d401c2..d0902c6edc 100644 --- a/src/core/component/field/init.ts +++ b/src/core/component/field/init.ts @@ -11,7 +11,7 @@ import type { ComponentInterface, ComponentField } from 'core/component/interfac /** * Initializes all fields of a given component instance. - * This function returns A dictionary containing the names of the initialized fields as keys, + * This function returns a dictionary containing the names of the initialized fields as keys, * with their corresponding initialized values as values. * * @param from - the dictionary where is stored the passed component fields, like `$fields` or `$systemFields` @@ -67,6 +67,8 @@ export function initFields( } else { store[name] = val; } + + unsafe.$activeField = undefined; }); return store; From 96b597b46f62c0f01416d6ed9c55fb068c67b626 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 16 Aug 2024 19:17:54 +0300 Subject: [PATCH 039/334] chore: stylish fixes --- src/core/component/field/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/field/README.md b/src/core/component/field/README.md index 54acee6fa2..f9f05ed5c7 100644 --- a/src/core/component/field/README.md +++ b/src/core/component/field/README.md @@ -20,5 +20,5 @@ developers can design and optimize their components for optimal performance and ### initFields Initializes all fields of a given component instance. -This function returns A dictionary containing the names of the initialized fields as keys, +This function returns a dictionary containing the names of the initialized fields as keys, with their corresponding initialized values as values. From aa330cf6c0584cf838e81abc437d617346ba6e66 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 17 Aug 2024 11:57:31 +0300 Subject: [PATCH 040/334] chore: fixed TS errors --- src/components/base/b-bottom-slide/b-bottom-slide.ts | 3 +-- src/components/base/b-dynamic-page/b-dynamic-page.ts | 3 +-- src/components/base/b-router/b-router.ts | 3 +-- src/components/base/b-tree/b-tree.ts | 3 +-- .../base/b-virtual-scroll-new/b-virtual-scroll-new.ts | 3 +-- .../base/b-virtual-scroll/b-virtual-scroll.ts | 4 ++-- .../base/b-virtual-scroll/modules/chunk-render.ts | 2 +- src/components/form/b-select/b-select.ts | 3 +-- .../b-friends-daemons-dummy/b-friends-daemons-dummy.ts | 2 +- .../daemons/test/b-friends-daemons-dummy/interface.ts | 6 ++++-- src/components/friends/friend/class.ts | 4 ++-- src/components/friends/sync/test/unit/object.ts | 2 +- src/components/super/i-block/i-block.ts | 3 +-- src/components/super/i-data/i-data.ts | 3 +-- src/components/super/i-input-text/i-input-text.ts | 3 +-- src/components/super/i-input/i-input.ts | 3 +-- src/core/component/interface/component/component.ts | 2 +- src/core/component/interface/component/unsafe.ts | 10 ++++++---- 18 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/components/base/b-bottom-slide/b-bottom-slide.ts b/src/components/base/b-bottom-slide/b-bottom-slide.ts index 61edeaa4db..129192064b 100644 --- a/src/components/base/b-bottom-slide/b-bottom-slide.ts +++ b/src/components/base/b-bottom-slide/b-bottom-slide.ts @@ -48,8 +48,7 @@ interface bBottomSlide extends Trait, Trait> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index e1e46a7669..17fa9d67f4 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -209,8 +209,7 @@ export default class bDynamicPage extends iDynamicPage { } } - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/base/b-router/b-router.ts b/src/components/base/b-router/b-router.ts index 83dbd2d8f0..0020c2a9ce 100644 --- a/src/components/base/b-router/b-router.ts +++ b/src/components/base/b-router/b-router.ts @@ -92,8 +92,7 @@ export default class bRouter extends iRouterProps { @system() private previousTransitionOptions: Nullable; - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/base/b-tree/b-tree.ts b/src/components/base/b-tree/b-tree.ts index 49abc8c6fe..a35c0be83b 100644 --- a/src/components/base/b-tree/b-tree.ts +++ b/src/components/base/b-tree/b-tree.ts @@ -53,8 +53,7 @@ interface bTree extends Trait, Trait {} @derive(iActiveItems, iFoldable) class bTree extends iTreeProps implements iActiveItems, iFoldable { - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts index ebde54f176..234c23ee45 100644 --- a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts +++ b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts @@ -96,8 +96,7 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements }; } - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/base/b-virtual-scroll/b-virtual-scroll.ts b/src/components/base/b-virtual-scroll/b-virtual-scroll.ts index 4c49fce984..6aa85c46dc 100644 --- a/src/components/base/b-virtual-scroll/b-virtual-scroll.ts +++ b/src/components/base/b-virtual-scroll/b-virtual-scroll.ts @@ -155,6 +155,7 @@ export default class bVirtualScroll extends iData implements iItems { readonly requestQuery?: RequestQueryFn; @prop({type: [Object, Array], required: false}) + // @ts-ignore (override) override readonly request?: RequestParams; /** @@ -186,8 +187,7 @@ export default class bVirtualScroll extends iData implements iItems { this.field.set('itemsStore', value); } - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/base/b-virtual-scroll/modules/chunk-render.ts b/src/components/base/b-virtual-scroll/modules/chunk-render.ts index cde89a216b..6a9b34eba8 100644 --- a/src/components/base/b-virtual-scroll/modules/chunk-render.ts +++ b/src/components/base/b-virtual-scroll/modules/chunk-render.ts @@ -93,7 +93,7 @@ export default class ChunkRender extends Friend { return Math.floor((Math.random() * (0.06 - 0.01) + 0.01) * 100) / 100; } - constructor(component: iBlock) { + constructor(component: iBlock | iBlock['unsafe']) { super(component); this.component.on('hook:mounted', this.initEventHandlers.bind(this)); } diff --git a/src/components/form/b-select/b-select.ts b/src/components/form/b-select/b-select.ts index aeb64e825a..505304244a 100644 --- a/src/components/form/b-select/b-select.ts +++ b/src/components/form/b-select/b-select.ts @@ -75,8 +75,7 @@ interface bSelect extends Trait, Trait, @derive(SelectEventHandlers, iOpenToggle, iActiveItems) class bSelect extends iSelectProps implements iOpenToggle, iActiveItems { - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts b/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts index be80e44e37..0cc24b3118 100644 --- a/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts +++ b/src/components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy.ts @@ -22,7 +22,7 @@ interface bFriendsDaemonsDummy extends Dictionary {} @component() class bFriendsDaemonsDummy extends bDummy { // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/friends/daemons/test/b-friends-daemons-dummy/interface.ts b/src/components/friends/daemons/test/b-friends-daemons-dummy/interface.ts index e84af1ba70..a6ac26f7e3 100644 --- a/src/components/friends/daemons/test/b-friends-daemons-dummy/interface.ts +++ b/src/components/friends/daemons/test/b-friends-daemons-dummy/interface.ts @@ -10,7 +10,9 @@ import type { UnsafeIData } from 'components/dummies/b-dummy/b-dummy'; import type bFriendsDaemonsDummy from 'components/friends/daemons/test/b-friends-daemons-dummy/b-friends-daemons-dummy'; -export interface UnsafeBFriendsDaemonsDummy - extends UnsafeIData { +export interface UnsafeBFriendsDaemonsDummy< + CTX extends bFriendsDaemonsDummy = bFriendsDaemonsDummy + // @ts-ignore (override) +> extends UnsafeIData { daemons: CTX['daemons']; } diff --git a/src/components/friends/friend/class.ts b/src/components/friends/friend/class.ts index 81eb08fba4..b00f9e9dbb 100644 --- a/src/components/friends/friend/class.ts +++ b/src/components/friends/friend/class.ts @@ -122,8 +122,8 @@ export default class Friend { } constructor(component: iBlock | iBlock['unsafe']) { - this.ctx = Object.cast(component.unsafe); - this.component = Object.cast(component); + this.component = Object.cast(component); + this.ctx = Object.cast(component); this.ctx.$async.worker(() => { const that = this; diff --git a/src/components/friends/sync/test/unit/object.ts b/src/components/friends/sync/test/unit/object.ts index 6b68c5f972..f5b9f4939b 100644 --- a/src/components/friends/sync/test/unit/object.ts +++ b/src/components/friends/sync/test/unit/object.ts @@ -295,7 +295,7 @@ test.describe('friends/sync `object`', () => { test.expect(scan).toEqual([1, {bla: 1}, {bla: 2}, {bla: 3}, {bla: undefined}]); }); - test([ + test.only([ 'should create a link for the nested property in the mounted watcher', 'when the JavaScript link to this property is specified as the source' ].join(' '), async () => { diff --git a/src/components/super/i-block/i-block.ts b/src/components/super/i-block/i-block.ts index 8bfe3219cc..0670a32ddf 100644 --- a/src/components/super/i-block/i-block.ts +++ b/src/components/super/i-block/i-block.ts @@ -56,8 +56,7 @@ export { Classes, ModVal, ModsDecl, ModsProp, ModsDict }; @component() export default abstract class iBlock extends iBlockProviders { - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/super/i-data/i-data.ts b/src/components/super/i-data/i-data.ts index b403ce3d82..efbc2dd819 100644 --- a/src/components/super/i-data/i-data.ts +++ b/src/components/super/i-data/i-data.ts @@ -59,8 +59,7 @@ const @component({functional: null}) export default abstract class iData extends iDataHandlers { - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/super/i-input-text/i-input-text.ts b/src/components/super/i-input-text/i-input-text.ts index 86ce7b66c0..c238f26b08 100644 --- a/src/components/super/i-input-text/i-input-text.ts +++ b/src/components/super/i-input-text/i-input-text.ts @@ -189,8 +189,7 @@ export default class iInputText extends iInput implements iWidth, iSize { @prop({type: Object, required: false}) readonly regExps?: Dictionary; - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/super/i-input/i-input.ts b/src/components/super/i-input/i-input.ts index 08628170b4..a5f101fbdc 100644 --- a/src/components/super/i-input/i-input.ts +++ b/src/components/super/i-input/i-input.ts @@ -88,8 +88,7 @@ export default abstract class iInput extends iInputHandlers implements iVisible, ] }; - // @ts-ignore (override) - override get unsafe(): UnsafeGetter> { + override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index b4dda2e680..ecca4e267f 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -177,7 +177,7 @@ export abstract class ComponentInterface { * causing TypeScript errors. * Use it when you need to decompose the component class into a composition of friendly classes. */ - get unsafe(): UnsafeGetter> { + get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/core/component/interface/component/unsafe.ts b/src/core/component/interface/component/unsafe.ts index 2c07466b27..eff947ce80 100644 --- a/src/core/component/interface/component/unsafe.ts +++ b/src/core/component/interface/component/unsafe.ts @@ -12,16 +12,18 @@ import type { ComponentInterface } from 'core/component/interface/component/comp * A helper structure to pack the unsafe interface. * It resolves some ambiguous TS warnings. */ -export type UnsafeGetter< - CTX extends ComponentInterface, - U extends UnsafeComponentInterface = UnsafeComponentInterface -> = Dictionary & Overwrite & {unsafe: any}; +export type UnsafeGetter = U['CTX'] & U; /** * This is a special interface that provides access to protected properties and methods outside the primary class. * It is used to create friendly classes. */ export interface UnsafeComponentInterface { + /** + * Type: the context type + */ + readonly CTX: Omit; + // @ts-ignore (access) meta: CTX['meta']; From b2f65b36a623a781b41c3a401b00120167aa7d7b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 17 Aug 2024 15:21:40 +0300 Subject: [PATCH 041/334] chore: remove test.only --- src/components/friends/sync/test/unit/object.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/friends/sync/test/unit/object.ts b/src/components/friends/sync/test/unit/object.ts index f5b9f4939b..6b68c5f972 100644 --- a/src/components/friends/sync/test/unit/object.ts +++ b/src/components/friends/sync/test/unit/object.ts @@ -295,7 +295,7 @@ test.describe('friends/sync `object`', () => { test.expect(scan).toEqual([1, {bla: 1}, {bla: 2}, {bla: 3}, {bla: undefined}]); }); - test.only([ + test([ 'should create a link for the nested property in the mounted watcher', 'when the JavaScript link to this property is specified as the source' ].join(' '), async () => { From fb48b78f5ce86787bc0a5b0d7b395029824c8ab7 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 20 Aug 2024 14:56:35 +0300 Subject: [PATCH 042/334] chore: fixed TS errors --- src/components/base/b-list/b-list.ts | 41 ++- src/components/base/b-list/interface.ts | 16 +- .../b-virtual-scroll-new.ts | 31 +- .../base/b-virtual-scroll-new/handlers.ts | 267 -------------- .../b-virtual-scroll-new/interface/common.ts | 13 + .../modules/emitter/index.ts | 2 +- .../modules/factory/index.ts | 17 +- .../b-virtual-scroll-new/modules/handlers.ts | 327 ++++++++++++++++++ .../observer/engines/intersection-observer.ts | 3 +- .../modules/observer/index.ts | 3 +- .../test/api/component-object/index.ts | 2 +- .../test/unit/functional/rendering/default.ts | 2 +- .../test/unit/scenario/reload.ts | 2 +- .../directives/bind-with/helpers.ts | 19 +- src/components/form/b-input/b-input.ts | 9 +- src/components/form/b-select/interface.ts | 9 + src/components/form/b-textarea/b-textarea.ts | 3 +- .../b-friends-module-loader-dummy.ts | 3 +- src/components/global/g-hint/g-hint.ts | 9 + src/components/super/i-block/event/README.md | 4 +- src/components/super/i-block/event/index.ts | 4 +- .../super/i-block/modules/mods/index.ts | 32 +- .../b-super-i-block-destructor-dummy.ts | 8 +- .../super/i-block/test/unit/destructor.ts | 4 +- src/components/super/i-data/i-data.ts | 18 +- src/components/super/i-data/interface.ts | 17 - src/components/super/i-page/README.md | 6 +- src/components/super/i-page/i-page.ts | 14 +- .../super/i-static-page/i-static-page.ts | 3 +- .../traits/i-active-items/i-active-items.ts | 2 +- .../traits/i-data-provider/i-data-provider.ts | 13 +- .../traits/i-data-provider/interface.ts | 14 + .../component/decorators/interface/field.ts | 4 +- src/core/component/directives/ref/index.ts | 2 +- src/core/component/functional/helpers.ts | 5 +- .../component/init/states/before-create.ts | 3 +- .../interface/component/component.ts | 5 + .../component/interface/component/unsafe.ts | 3 + src/core/component/prop/helpers.ts | 2 +- src/core/init/create-app.ts | 9 +- 40 files changed, 550 insertions(+), 400 deletions(-) delete mode 100644 src/components/base/b-virtual-scroll-new/handlers.ts create mode 100644 src/components/base/b-virtual-scroll-new/modules/handlers.ts create mode 100644 src/components/global/g-hint/g-hint.ts diff --git a/src/components/base/b-list/b-list.ts b/src/components/base/b-list/b-list.ts index 70a0e0e478..13002d8c7c 100644 --- a/src/components/base/b-list/b-list.ts +++ b/src/components/base/b-list/b-list.ts @@ -23,13 +23,27 @@ import iWidth from 'components/traits/i-width/i-width'; import iItems, { IterationKey } from 'components/traits/i-items/i-items'; import iActiveItems from 'components/traits/i-active-items/i-active-items'; -import { component, field, system, computed, hook, watch, ModsDecl } from 'components/super/i-data/i-data'; +import { + + component, + + field, + system, + computed, + + hook, + watch, + + ModsDecl, + UnsafeGetter + +} from 'components/super/i-data/i-data'; import iListProps from 'components/base/b-list/props'; import Values from 'components/base/b-list/modules/values'; import { setActiveMod, normalizeItems } from 'components/base/b-list/modules/helpers'; -import type { Items } from 'components/base/b-list/interface'; +import type { Items, UnsafeBList } from 'components/base/b-list/interface'; export * from 'components/super/i-data/i-data'; export * from 'components/base/b-list/interface'; @@ -51,8 +65,7 @@ interface bList extends Trait {} class bList extends iListProps implements iVisible, iWidth, iActiveItems { /** {@link bList.attrsProp} */ get attrs(): Dictionary { - const - attrs = {...this.attrsProp}; + const attrs = {...this.attrsProp}; if (this.items.some((el) => el.href === undefined)) { attrs.role = 'tablist'; @@ -87,14 +100,6 @@ class bList extends iListProps implements iVisible, iWidth, iActiveItems { @system() readonly activeChangeEvent: string = 'change'; - /** - * {@link iActiveItems.active} - * {@link bList.activeStore} - */ - get active(): this['Active'] { - return iActiveItems.getActive(this); - } - /** * {@link iActiveItems.activeStore} * {@link bList.activeProp} @@ -109,6 +114,18 @@ class bList extends iListProps implements iVisible, iWidth, iActiveItems { activeStore!: this['Active']; + /** + * {@link iActiveItems.active} + * {@link bList.activeStore} + */ + get active(): this['Active'] { + return iActiveItems.getActive(this); + } + + override get unsafe(): UnsafeGetter> { + return Object.cast(this); + } + static override readonly mods: ModsDecl = { ...iVisible.mods, ...iWidth.mods, diff --git a/src/components/base/b-list/interface.ts b/src/components/base/b-list/interface.ts index 9e27c5cbec..bf31647e04 100644 --- a/src/components/base/b-list/interface.ts +++ b/src/components/base/b-list/interface.ts @@ -6,9 +6,12 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type { HintPosition } from 'components/global/g-hint'; -import type { ModsDict } from 'components/super/i-data/i-data'; +import type { HintPosition } from 'components/global/g-hint/g-hint'; + +import type { ModsDict, UnsafeIData } from 'components/super/i-data/i-data'; + import type { Item as Super } from 'components/traits/i-active-items/i-active-items'; +import type bList from 'components/base/b-list/b-list'; export type Items = Item[]; @@ -89,3 +92,12 @@ export interface Item extends Super { */ attrs?: Dictionary; } + +// @ts-ignore (extend) +export interface UnsafeBList extends UnsafeIData { + // @ts-ignore (access) + itemsStore: CTX['itemsStore']; + + // @ts-ignore (access) + normalizeItems: CTX['normalizeItems']; +} diff --git a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts index 234c23ee45..675132766f 100644 --- a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts +++ b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts @@ -12,13 +12,18 @@ */ import symbolGenerator from 'core/symbol'; +import { derive } from 'core/functools/trait'; + import type { AsyncOptions } from 'core/async'; import SyncPromise from 'core/promise/sync'; import type iItems from 'components/traits/i-items/i-items'; import DOM, { watchForIntersection } from 'components/friends/dom'; import VDOM, { create, render } from 'components/friends/vdom'; -import { iVirtualScrollHandlers } from 'components/base/b-virtual-scroll-new/handlers'; +import iVirtualScrollProps from 'components/base/b-virtual-scroll-new/props'; + +import iVirtualScrollHandlers from 'components/base/b-virtual-scroll-new/modules/handlers'; + import { bVirtualScrollNewAsyncGroup, @@ -48,6 +53,7 @@ import { ComponentInternalState } from 'components/base/b-virtual-scroll-new/mod import { SlotsStateController } from 'components/base/b-virtual-scroll-new/modules/slots'; import { ComponentFactory } from 'components/base/b-virtual-scroll-new/modules/factory'; import { Observer } from 'components/base/b-virtual-scroll-new/modules/observer'; +import { isAsyncReplaceError } from 'components/base/b-virtual-scroll-new/modules/helpers'; import iData, { component, system, field, watch, wait, RequestParams, UnsafeGetter } from 'components/super/i-data/i-data'; @@ -60,9 +66,11 @@ const $$ = symbolGenerator(); DOM.addToPrototype({watchForIntersection}); VDOM.addToPrototype({create, render}); -@component() -export default class bVirtualScrollNew extends iVirtualScrollHandlers implements iItems { +interface bVirtualScrollNew extends Trait {} +@component() +@derive(iVirtualScrollHandlers) +class bVirtualScrollNew extends iVirtualScrollProps implements iItems { /** {@link componentTypedEmitter} */ @system((ctx) => componentTypedEmitter(ctx)) protected readonly componentEmitter!: ComponentTypedEmitter; @@ -256,6 +264,21 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements this.onItemsInit(this.items); } + protected override onRequestError(this: bVirtualScrollNew, ...args: Parameters): ReturnType { + const + err = args[0]; + + if (isAsyncReplaceError(err)) { + return; + } + + const + state = this.getVirtualScrollState(); + + this.onDataLoadError(state.isInitialLoading); + return super.onRequestError(err, this.initLoad.bind(this)); + } + protected override convertDataToDB(data: unknown): O | this['DB'] { this.onConvertDataToDB(data); const result = super.convertDataToDB(data); @@ -514,3 +537,5 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements }).catch(stderr); } } + +export default bVirtualScrollNew; diff --git a/src/components/base/b-virtual-scroll-new/handlers.ts b/src/components/base/b-virtual-scroll-new/handlers.ts deleted file mode 100644 index cd2503a956..0000000000 --- a/src/components/base/b-virtual-scroll-new/handlers.ts +++ /dev/null @@ -1,267 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import symbolGenerator from 'core/symbol'; - -import iVirtualScrollProps from 'components/base/b-virtual-scroll-new/props'; - -import type bVirtualScrollNew from 'components/base/b-virtual-scroll-new/b-virtual-scroll-new'; -import type { MountedChild } from 'components/base/b-virtual-scroll-new/interface'; - -import { bVirtualScrollNewAsyncGroup, componentEvents, componentLocalEvents } from 'components/base/b-virtual-scroll-new/const'; -import { isAsyncReplaceError } from 'components/base/b-virtual-scroll-new/modules/helpers'; - -import iData, { component } from 'components/super/i-data/i-data'; - -const $$ = symbolGenerator(); - -/** - * A class that provides an API to handle events emitted by the {@link bVirtualScrollNew} component. - * This class is designed to work in conjunction with {@link bVirtualScrollNew}. - */ -@component({partial: 'bVirtualScrollNew'}) -export abstract class iVirtualScrollHandlers extends iVirtualScrollProps { - /** - * Handler: component reset event. - * Resets the component state to its initial state. - */ - protected onReset(this: bVirtualScrollNew): void { - this.componentInternalState.reset(); - this.observer.reset(); - - this.async.clearAll({group: new RegExp(bVirtualScrollNewAsyncGroup)}); - - this.componentEmitter.emit(componentEvents.resetState); - } - - /** - * Handler: render start event. - * Triggered when the component rendering starts. - */ - protected onRenderStart(this: bVirtualScrollNew): void { - this.componentInternalState.updateIsLastRender(); - this.componentEmitter.emit(componentEvents.renderStart); - } - - /** - * Handler: render engine start event. - * Triggered when the component rendering using the rendering engine starts. - */ - protected onRenderEngineStart(this: bVirtualScrollNew): void { - this.componentEmitter.emit(componentEvents.renderEngineStart); - } - - /** - * Handler: render engine done event. - * Triggered when the component rendering using the rendering engine is completed. - */ - protected onRenderEngineDone(this: bVirtualScrollNew): void { - this.componentEmitter.emit(componentEvents.renderEngineDone); - } - - /** - * Handler: DOM insert start event. - * Triggered when the insertion of rendered components into the DOM tree starts. - * - * @param childList - */ - protected onDomInsertStart(this: bVirtualScrollNew, childList: MountedChild[]): void { - this.componentInternalState.updateDataOffset(); - this.componentInternalState.updateMounted(childList); - this.componentInternalState.setIsInitialRender(false); - this.componentInternalState.incrementRenderPage(); - - this.componentEmitter.emit(componentEvents.domInsertStart); - } - - /** - * Handler: DOM insert done event. - * Triggered when the insertion of rendered components into the DOM tree is completed. - */ - protected onDomInsertDone(this: bVirtualScrollNew): void { - this.componentEmitter.emit(componentEvents.domInsertDone); - } - - /** - * Handler: render done event. - * Triggered when rendering is completed. - */ - protected onRenderDone(this: bVirtualScrollNew): void { - this.componentEmitter.emit(componentEvents.renderDone); - this.localEmitter.emit(componentLocalEvents.renderCycleDone); - } - - /** - * Handler: lifecycle done event. - * Triggered when the internal lifecycle of the component is completed. - */ - protected onLifecycleDone(this: bVirtualScrollNew): void { - const - state = this.getVirtualScrollState(), - isDomInsertInProgress = this.componentInternalState.getIsDomInsertInProgress(); - - if (state.isLifecycleDone) { - return; - } - - const handler = () => { - this.slotsStateController.doneState(); - this.componentInternalState.setIsLifecycleDone(true); - this.componentEmitter.emit(componentEvents.lifecycleDone); - }; - - if (isDomInsertInProgress) { - this.localEmitter.once(componentLocalEvents.renderCycleDone, handler, { - group: bVirtualScrollNewAsyncGroup, - label: $$.waitUntilRenderDone - }); - - return; - } - - return handler(); - } - - /** - * Handler: convert data to database event. - * Triggered when the loaded data is converted. - * - * @param data - the converted data. - */ - protected onConvertDataToDB(this: bVirtualScrollNew, data: unknown): void { - this.componentInternalState.setRawLastLoaded(data); - this.componentEmitter.emit(componentEvents.convertDataToDB, data); - } - - /** - * Handler: data load start event. - * Triggered when data loading starts. - * - * @param isInitialLoading - indicates whether it is an initial component loading. - */ - protected onDataLoadStart(this: bVirtualScrollNew, isInitialLoading: boolean): void { - this.componentInternalState.setIsLoadingInProgress(true); - this.componentInternalState.setIsLastErrored(false); - this.slotsStateController.loadingProgressState(isInitialLoading); - - this.componentEmitter.emit(componentEvents.dataLoadStart, isInitialLoading); - } - - /** - * Handler: data load success event. - * Triggered when data loading is successfully completed. - * - * @param isInitialLoading - indicates whether it is an initial component loading. - * @param data - the loaded data. - * @throws {@link ReferenceError} if the loaded data does not have a "data" field. - */ - protected onDataLoadSuccess(this: bVirtualScrollNew, isInitialLoading: boolean, data: unknown): void { - this.componentInternalState.setIsLoadingInProgress(false); - - const - dataToProvide = Object.isPlainObject(data) ? data.data : data; - - if (!Array.isArray(dataToProvide)) { - throw new ReferenceError('Missing data to perform render'); - } - - this.componentInternalState.updateData(dataToProvide, isInitialLoading); - this.componentInternalState.incrementLoadPage(); - - const - isRequestsStopped = this.shouldStopRequestingDataWrapper(); - - this.componentEmitter.emit(componentEvents.dataLoadSuccess, dataToProvide, isInitialLoading); - this.slotsStateController.loadingSuccessState(); - - if ( - isInitialLoading && - isRequestsStopped && - Object.size(dataToProvide) === 0 - ) { - this.onDataEmpty(); - this.onLifecycleDone(); - - } else { - this.loadDataOrPerformRender(); - } - } - - /** - * Handler: data load error event. - * Triggered when data loading fails. - * - * @param isInitialLoading - indicates whether it is an initial component loading. - */ - protected onDataLoadError(this: bVirtualScrollNew, isInitialLoading: boolean): void { - this.componentInternalState.setIsLoadingInProgress(false); - this.componentInternalState.setIsLastErrored(true); - this.slotsStateController.loadingFailedState(); - - this.componentEmitter.emit(componentEvents.dataLoadError, isInitialLoading); - } - - protected override onRequestError(this: bVirtualScrollNew, ...args: Parameters): ReturnType { - const - err = args[0]; - - if (isAsyncReplaceError(err)) { - return; - } - - const - state = this.getVirtualScrollState(); - - this.onDataLoadError(state.isInitialLoading); - return super.onRequestError(err, this.initLoad.bind(this)); - } - - /** - * Handler: data empty event. - * Triggered when the loaded data is empty. - */ - protected onDataEmpty(this: bVirtualScrollNew): void { - this.slotsStateController.emptyState(); - - this.componentEmitter.emit(componentEvents.dataLoadEmpty); - } - - /** - * Handler: component enters the viewport - * @param component - the component that enters the viewport. - */ - protected onElementEnters(this: bVirtualScrollNew, component: MountedChild): void { - this.componentInternalState.setMaxViewedIndex(component); - this.loadDataOrPerformRender(); - - this.componentEmitter.emit(componentEvents.elementEnter, component); - } - - /** - * Handler: The tombstones slot entered the viewport - */ - protected onTombstonesEnter(this: bVirtualScrollNew): void { - this.componentInternalState.setIsTombstonesInView(true); - this.loadDataOrPerformRender(); - } - - /** - * Handler: The tombstones slot leaves the viewport - */ - protected onTombstonesLeave(this: bVirtualScrollNew): void { - this.componentInternalState.setIsTombstonesInView(false); - } - - /** - * Handler: items to render was updated - * @param items - */ - protected onItemsInit(this: bVirtualScrollNew, items: Exclude): void { - this.onDataLoadSuccess(true, items); - } -} diff --git a/src/components/base/b-virtual-scroll-new/interface/common.ts b/src/components/base/b-virtual-scroll-new/interface/common.ts index 7db61d5c29..880ac32426 100644 --- a/src/components/base/b-virtual-scroll-new/interface/common.ts +++ b/src/components/base/b-virtual-scroll-new/interface/common.ts @@ -64,18 +64,31 @@ export interface ShouldPerform { export interface UnsafeBVirtualScroll extends UnsafeIData { // @ts-ignore (access) onRenderEngineStart: CTX['onRenderEngineStart']; + // @ts-ignore (access) onRenderEngineDone: CTX['onRenderEngineDone']; + // @ts-ignore (access) onElementEnters: CTX['onElementEnters']; + // @ts-ignore (access) componentEmitter: CTX['componentEmitter']; + // @ts-ignore (access) slotsStateController: CTX['slotsStateController']; + // @ts-ignore (access) componentInternalState: CTX['componentInternalState']; + // @ts-ignore (access) componentFactory: CTX['componentFactory']; + + // @ts-ignore (access) + shouldStopRequestingDataWrapper: CTX['shouldStopRequestingDataWrapper']; + + // @ts-ignore (access) + loadDataOrPerformRender: CTX['loadDataOrPerformRender']; + // @ts-ignore (access) observer: CTX['observer']; } diff --git a/src/components/base/b-virtual-scroll-new/modules/emitter/index.ts b/src/components/base/b-virtual-scroll-new/modules/emitter/index.ts index c1e84373b4..66cbed4fab 100644 --- a/src/components/base/b-virtual-scroll-new/modules/emitter/index.ts +++ b/src/components/base/b-virtual-scroll-new/modules/emitter/index.ts @@ -18,7 +18,7 @@ export * from 'components/base/b-virtual-scroll-new/modules/emitter/interface'; * Provides methods for interacting with the `selfEmitter` using typed events * @param ctx */ -export function componentTypedEmitter(ctx: bVirtualScrollNew): ComponentTypedEmitter { +export function componentTypedEmitter(ctx: bVirtualScrollNew['unsafe']): ComponentTypedEmitter { const once = ( event: EVENT, handler: (...args: LocalEventPayload) => void, diff --git a/src/components/base/b-virtual-scroll-new/modules/factory/index.ts b/src/components/base/b-virtual-scroll-new/modules/factory/index.ts index fe5a5bb541..700ca93c44 100644 --- a/src/components/base/b-virtual-scroll-new/modules/factory/index.ts +++ b/src/components/base/b-virtual-scroll-new/modules/factory/index.ts @@ -30,10 +30,11 @@ export class ComponentFactory extends Friend { * Returns an array of component items. */ produceComponentItems(): ComponentItem[] { + const {ctx} = this; + const - {ctx} = this, normalize = this.normalizeComponentItem.bind(this), - componentItems = ctx.itemsFactory(ctx.getVirtualScrollState(), ctx); + componentItems = ctx.itemsFactory(ctx.getVirtualScrollState(), this.component); return this.itemsProcessor(componentItems).map(normalize); } @@ -99,20 +100,20 @@ export class ComponentFactory extends Friend { * @param items - the list of items to process. */ protected itemsProcessor(items: ComponentItem[]): ComponentItem[] { - const - {ctx} = this, - itemsProcessors = ctx.getItemsProcessors(); + const {ctx, component} = this; + + const itemsProcessors = ctx.getItemsProcessors(); if (!itemsProcessors) { return items; } if (Object.isFunction(itemsProcessors)) { - return itemsProcessors(items, ctx); + return itemsProcessors(items, component); } Object.forEach(itemsProcessors, (processor) => { - items = processor(items, ctx); + items = processor(items, component); }); return items; @@ -174,7 +175,7 @@ export class ComponentFactory extends Friend { ctx.onRenderEngineStart(); const - res = vdomRender.render(ctx, descriptors); + res = vdomRender.render(this.component, descriptors); ctx.onRenderEngineDone(); diff --git a/src/components/base/b-virtual-scroll-new/modules/handlers.ts b/src/components/base/b-virtual-scroll-new/modules/handlers.ts new file mode 100644 index 0000000000..51e36db24a --- /dev/null +++ b/src/components/base/b-virtual-scroll-new/modules/handlers.ts @@ -0,0 +1,327 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import symbolGenerator from 'core/symbol'; + +import type bVirtualScrollNew from 'components/base/b-virtual-scroll-new/b-virtual-scroll-new'; + +import { bVirtualScrollNewAsyncGroup, componentEvents, componentLocalEvents } from 'components/base/b-virtual-scroll-new/const'; +import type { MountedChild } from 'components/base/b-virtual-scroll-new/interface'; + +const $$ = symbolGenerator(); + +type TraitType = bVirtualScrollNew['unsafe'] & iVirtualScrollHandlers; + +export default abstract class iVirtualScrollHandlers { + /** {@link iVirtualScrollHandlers.prototype.onReset} */ + static onReset(ctx: TraitType): void { + ctx.componentInternalState.reset(); + ctx.observer.reset(); + ctx.async.clearAll({group: new RegExp(bVirtualScrollNewAsyncGroup)}); + ctx.componentEmitter.emit(componentEvents.resetState); + } + + /** {@link iVirtualScrollHandlers.prototype.onRenderStart} */ + static onRenderStart(ctx: TraitType): void { + ctx.componentInternalState.updateIsLastRender(); + ctx.componentEmitter.emit(componentEvents.renderStart); + } + + /** {@link iVirtualScrollHandlers.prototype.onRenderEngineStart} */ + static onRenderEngineStart(ctx: TraitType): void { + ctx.componentEmitter.emit(componentEvents.renderEngineStart); + } + + /** {@link iVirtualScrollHandlers.prototype.onRenderEngineDone} */ + static onRenderEngineDone(ctx: TraitType): void { + ctx.componentEmitter.emit(componentEvents.renderEngineDone); + } + + /** {@link iVirtualScrollHandlers.prototype.onDomInsertStart} */ + static onDomInsertStart(ctx: TraitType, childList: MountedChild[]): void { + ctx.componentInternalState.updateDataOffset(); + ctx.componentInternalState.updateMounted(childList); + ctx.componentInternalState.setIsInitialRender(false); + ctx.componentInternalState.incrementRenderPage(); + + ctx.componentEmitter.emit(componentEvents.domInsertStart); + } + + /** {@link iVirtualScrollHandlers.prototype.onDomInsertDone} */ + static onDomInsertDone(ctx: TraitType): void { + ctx.componentEmitter.emit(componentEvents.domInsertDone); + } + + /** {@link iVirtualScrollHandlers.prototype.onRenderDone} */ + static onRenderDone(ctx: TraitType): void { + ctx.componentEmitter.emit(componentEvents.renderDone); + ctx.localEmitter.emit(componentLocalEvents.renderCycleDone); + } + + /** {@link iVirtualScrollHandlers.prototype.onLifecycleDone} */ + static onLifecycleDone(ctx: TraitType): void { + const + state = ctx.getVirtualScrollState(), + isDomInsertInProgress = ctx.componentInternalState.getIsDomInsertInProgress(); + + if (state.isLifecycleDone) { + return; + } + + const handler = () => { + ctx.slotsStateController.doneState(); + ctx.componentInternalState.setIsLifecycleDone(true); + ctx.componentEmitter.emit(componentEvents.lifecycleDone); + }; + + if (isDomInsertInProgress) { + ctx.localEmitter.once(componentLocalEvents.renderCycleDone, handler, { + group: bVirtualScrollNewAsyncGroup, + label: $$.waitUntilRenderDone + }); + + return; + } + + return handler(); + } + + /** {@link iVirtualScrollHandlers.prototype.onConvertDataToDB} */ + static onConvertDataToDB(ctx: TraitType, data: unknown): void { + ctx.componentInternalState.setRawLastLoaded(data); + ctx.componentEmitter.emit(componentEvents.convertDataToDB, data); + } + + /** {@link iVirtualScrollHandlers.prototype.onDataLoadStart} */ + static onDataLoadStart(ctx: TraitType, isInitialLoading: boolean): void { + ctx.componentInternalState.setIsLoadingInProgress(true); + ctx.componentInternalState.setIsLastErrored(false); + ctx.slotsStateController.loadingProgressState(isInitialLoading); + ctx.componentEmitter.emit(componentEvents.dataLoadStart, isInitialLoading); + } + + // eslint-disable-next-line jsdoc/require-throws + /** {@link iVirtualScrollHandlers.prototype.onDataLoadSuccess} */ + static onDataLoadSuccess(ctx: TraitType, isInitialLoading: boolean, data: unknown): void { + ctx.componentInternalState.setIsLoadingInProgress(false); + + const + dataToProvide = Object.isPlainObject(data) ? data.data : data; + + if (!Array.isArray(dataToProvide)) { + throw new ReferenceError('Missing data to perform render'); + } + + ctx.componentInternalState.updateData(dataToProvide, isInitialLoading); + ctx.componentInternalState.incrementLoadPage(); + + const + isRequestsStopped = ctx.shouldStopRequestingDataWrapper(); + + ctx.componentEmitter.emit(componentEvents.dataLoadSuccess, dataToProvide, isInitialLoading); + ctx.slotsStateController.loadingSuccessState(); + + if ( + isInitialLoading && + isRequestsStopped && + Object.size(dataToProvide) === 0 + ) { + ctx.onDataEmpty(); + ctx.onLifecycleDone(); + + } else { + ctx.loadDataOrPerformRender(); + } + } + + /** {@link iVirtualScrollHandlers.prototype.onDataLoadError} */ + static onDataLoadError(ctx: TraitType, isInitialLoading: boolean): void { + ctx.componentInternalState.setIsLoadingInProgress(false); + ctx.componentInternalState.setIsLastErrored(true); + ctx.slotsStateController.loadingFailedState(); + + ctx.componentEmitter.emit(componentEvents.dataLoadError, isInitialLoading); + } + + /** {@link iVirtualScrollHandlers.prototype.onDataEmpty} */ + static onDataEmpty(ctx: TraitType): void { + ctx.slotsStateController.emptyState(); + + ctx.componentEmitter.emit(componentEvents.dataLoadEmpty); + } + + /** {@link iVirtualScrollHandlers.prototype.onElementEnters} */ + static onElementEnters(ctx: TraitType, component: MountedChild): void { + ctx.componentInternalState.setMaxViewedIndex(component); + ctx.loadDataOrPerformRender(); + + ctx.componentEmitter.emit(componentEvents.elementEnter, component); + } + + /** {@link iVirtualScrollHandlers.prototype.onTombstonesEnter} */ + static onTombstonesEnter(ctx: TraitType): void { + ctx.componentInternalState.setIsTombstonesInView(true); + ctx.loadDataOrPerformRender(); + } + + /** {@link iVirtualScrollHandlers.prototype.onTombstonesLeave} */ + static onTombstonesLeave(ctx: TraitType): void { + ctx.componentInternalState.setIsTombstonesInView(false); + } + + /** {@link iVirtualScrollHandlers.prototype.onItemsInit} */ + static onItemsInit(ctx: TraitType, items: Exclude): void { + ctx.onDataLoadSuccess(true, items); + } + + /** + * Handler: component reset event. + * Resets the component state to its initial state. + */ + onReset(): void { + return Object.throw(); + } + + /** + * Handler: render start event. + * Triggered when the component rendering starts. + */ + onRenderStart(): void { + return Object.throw(); + } + + /** + * Handler: render engine start event. + * Triggered when the component rendering using the rendering engine starts. + */ + onRenderEngineStart(): void { + return Object.throw(); + } + + /** + * Handler: render engine done event. + * Triggered when the component rendering using the rendering engine is completed. + */ + onRenderEngineDone(): void { + return Object.throw(); + } + + /** + * Handler: DOM insert start event. + * Triggered when the insertion of rendered components into the DOM tree starts. + * + * @param _childList + */ + onDomInsertStart(_childList: MountedChild[]): void { + return Object.throw(); + } + + /** + * Handler: DOM insert done event. + * Triggered when the insertion of rendered components into the DOM tree is completed. + */ + onDomInsertDone(): void { + return Object.throw(); + } + + /** + * Handler: render done event. + * Triggered when rendering is completed. + */ + onRenderDone(): void { + return Object.throw(); + } + + /** + * Handler: lifecycle done event. + * Triggered when the internal lifecycle of the component is completed. + */ + onLifecycleDone(): void { + return Object.throw(); + } + + /** + * Handler: convert data to database event. + * Triggered when the loaded data is converted. + * + * @param _data - the converted data. + */ + onConvertDataToDB(_data: unknown): void { + return Object.throw(); + } + + /** + * Handler: data load start event. + * Triggered when data loading starts. + * + * @param _isInitialLoading - indicates whether it is an initial component loading. + */ + onDataLoadStart(_isInitialLoading: boolean): void { + return Object.throw(); + } + + /** + * Handler: data load success event. + * Triggered when data loading is successfully completed. + * + * @param _isInitialLoading - indicates whether it is an initial component loading. + * @param _data - the loaded data. + * @throws {@link ReferenceError} if the loaded data does not have a "data" field. + */ + onDataLoadSuccess(_isInitialLoading: boolean, _data: unknown): void { + return Object.throw(); + } + + /** + * Handler: data load error event. + * Triggered when data loading fails. + * + * @param _isInitialLoading - indicates whether it is an initial component loading. + */ + onDataLoadError(_isInitialLoading: boolean): void { + return Object.throw(); + } + + /** + * Handler: data empty event. + * Triggered when the loaded data is empty. + */ + onDataEmpty(): void { + return Object.throw(); + } + + /** + * Handler: component enters the viewport + * @param _component - the component that enters the viewport. + */ + onElementEnters(_component: MountedChild): void { + return Object.throw(); + } + + /** + * Handler: the tombstone's slot entered the viewport + */ + onTombstonesEnter(): void { + return Object.throw(); + } + + /** + * Handler: the tombstone's slot leaves the viewport + */ + onTombstonesLeave(): void { + return Object.throw(); + } + + /** + * Handler: items to render was updated + * @param _items + */ + onItemsInit(_items: Exclude): void { + return Object.throw(); + } +} diff --git a/src/components/base/b-virtual-scroll-new/modules/observer/engines/intersection-observer.ts b/src/components/base/b-virtual-scroll-new/modules/observer/engines/intersection-observer.ts index 717efca506..4662295c1b 100644 --- a/src/components/base/b-virtual-scroll-new/modules/observer/engines/intersection-observer.ts +++ b/src/components/base/b-virtual-scroll-new/modules/observer/engines/intersection-observer.ts @@ -23,8 +23,7 @@ export default class IoObserver extends Friend implements ObserverEngine { * @param components */ watchForIntersection(components: MountedChild[]): void { - const - {ctx} = this; + const {ctx} = this; for (const component of components) { ctx.dom.watchForIntersection(component.node, { diff --git a/src/components/base/b-virtual-scroll-new/modules/observer/index.ts b/src/components/base/b-virtual-scroll-new/modules/observer/index.ts index 590f3114dd..d34b9a6450 100644 --- a/src/components/base/b-virtual-scroll-new/modules/observer/index.ts +++ b/src/components/base/b-virtual-scroll-new/modules/observer/index.ts @@ -31,9 +31,8 @@ export class Observer extends Friend { /** * @param ctx - the `bVirtualScrollNew` component instance. */ - constructor(ctx: bVirtualScrollNew) { + constructor(ctx: bVirtualScrollNew | bVirtualScrollNew['unsafe']) { super(ctx); - this.engine = new IoObserver(ctx); } diff --git a/src/components/base/b-virtual-scroll-new/test/api/component-object/index.ts b/src/components/base/b-virtual-scroll-new/test/api/component-object/index.ts index f51cb29731..723e97ebca 100644 --- a/src/components/base/b-virtual-scroll-new/test/api/component-object/index.ts +++ b/src/components/base/b-virtual-scroll-new/test/api/component-object/index.ts @@ -19,7 +19,7 @@ import { testStyles } from 'components/base/b-virtual-scroll-new/test/api/compon /** * The component object API for testing the {@link bVirtualScrollNew} component. */ -export class VirtualScrollComponentObject extends ComponentObject { +export class VirtualScrollComponentObject extends ComponentObject { /** * The locator for the container ref. */ diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/rendering/default.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/rendering/default.ts index 74503bdd73..6dbe86e274 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/rendering/default.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/rendering/default.ts @@ -159,7 +159,7 @@ test.describe('', () => { }); const - spy = await component.getSpy((ctx) => ctx.unsafe.componentFactory.produceNodes); + spy = await component.getSpy((ctx) => ctx.componentFactory.produceNodes); await test.expect(spy.callsCount).resolves.toBe(7); await test.expect(component.childList).toHaveCount(providerChunkSize); diff --git a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts index 89ee723ba7..6ab72117cf 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts @@ -115,7 +115,7 @@ test.describe('', () => { .build(); await component.waitForDataIndexChild(chunkSize - 1); - await component.evaluate((ctx, [event]) => ctx.unsafe.globalEmitter.emit(event), [event]); + await component.evaluate((ctx, [event]) => ctx.globalEmitter.emit(event), [event]); await component.waitForDataIndexChild(chunkSize * 2 - 1); const diff --git a/src/components/directives/bind-with/helpers.ts b/src/components/directives/bind-with/helpers.ts index 03c73dbf33..0ac3a908f0 100644 --- a/src/components/directives/bind-with/helpers.ts +++ b/src/components/directives/bind-with/helpers.ts @@ -26,12 +26,12 @@ export function getElementId(el: Element): string { * @param el - the element to which the directive is applied * @param ctx - the directive context */ -export function clearElementBindings(el: Element, ctx: Nullable): void { +export function clearElementBindings(el: Element, ctx: Nullable): void { if (ctx == null) { return; } - ctx.unsafe.async.clearAll({group: new RegExp(`:${getElementId(el)}`)}); + ctx.async.clearAll({group: new RegExp(`:${getElementId(el)}`)}); } /** @@ -44,7 +44,7 @@ export function clearElementBindings(el: Element, ctx: Nullable, el: Element, - ctx: Nullable + ctx: Nullable ): void { if (ctx == null || listener == null) { return; @@ -53,10 +53,7 @@ export function bindListenerToElement( const id = getElementId(el); - const { - unsafe, - unsafe: {async: $a} - } = ctx; + const {async: $a} = ctx; $a.clearAll({ group: new RegExp(`:${id}`) @@ -70,7 +67,7 @@ export function bindListenerToElement( if ('path' in listener) { const opts = listener.options ?? {}, - watcher = unsafe.$watch(listener.path, opts, handler); + watcher = ctx.$watch(listener.path, opts, handler); if (watcher != null) { $a.worker(watcher, group); @@ -90,9 +87,9 @@ export function bindListenerToElement( } const emitter = listener.emitter ?? { - on: unsafe.$on.bind(ctx), - once: unsafe.$once.bind(ctx), - off: unsafe.$off.bind(ctx) + on: ctx.$on.bind(ctx), + once: ctx.$once.bind(ctx), + off: ctx.$off.bind(ctx) }; if (listener.on != null) { diff --git a/src/components/form/b-input/b-input.ts b/src/components/form/b-input/b-input.ts index 784f9818f1..e930f9a693 100644 --- a/src/components/form/b-input/b-input.ts +++ b/src/components/form/b-input/b-input.ts @@ -40,8 +40,7 @@ export { default as InputValidators } from 'components/form/b-input/validators'; export { Value, FormValue }; -const - $$ = symbolGenerator(); +const $$ = symbolGenerator(); @component({ functional: { @@ -244,7 +243,7 @@ export default class bInput extends iInputText { return `${this.text}${this.textHint}`; } - @system({ + @system({ after: 'valueStore', init: (o) => o.sync.link((text) => { o.watch('valueProp', {label: $$.textStore}, () => { @@ -252,7 +251,7 @@ export default class bInput extends iInputText { label: $$.textStoreToValueStore }; - o.watch('valueStore', label, (v) => { + o.watch('valueStore', label, (v: CanUndef) => { o.async.clearAll(label); return link(v); }); @@ -262,7 +261,7 @@ export default class bInput extends iInputText { function link(textFromValue: CanUndef): string { const - resolvedText = textFromValue === undefined ? text ?? o.field.get('valueStore') : textFromValue, + resolvedText = textFromValue ?? text ?? o.field.get('valueStore'), str = resolvedText !== undefined ? String(resolvedText) : ''; if (o.isFunctional) { diff --git a/src/components/form/b-select/interface.ts b/src/components/form/b-select/interface.ts index e67dd73c67..7f5a77d6d1 100644 --- a/src/components/form/b-select/interface.ts +++ b/src/components/form/b-select/interface.ts @@ -61,6 +61,15 @@ export interface UnsafeBSelect extends UnsafeIInp // @ts-ignore (access) keydownHandlerEnabled: CTX['keydownHandlerEnabled']; + // @ts-ignore (access) + compiledMask: CTX['compiledMask']; + + // @ts-ignore (access) + itemsStore: CTX['itemsStore']; + + // @ts-ignore (access) + normalizeItems: CTX['normalizeItems']; + // @ts-ignore (access) setScrollToMarkedOrSelectedItem: CTX['setScrollToMarkedOrSelectedItem']; diff --git a/src/components/form/b-textarea/b-textarea.ts b/src/components/form/b-textarea/b-textarea.ts index 6587e44dc3..40f0e4806d 100644 --- a/src/components/form/b-textarea/b-textarea.ts +++ b/src/components/form/b-textarea/b-textarea.ts @@ -144,7 +144,7 @@ export default class bTextarea extends iInputText { // @ts-ignore (override) protected override valueStore!: this['Value']; - @system({ + @system({ after: 'valueStore', init: (o) => o.sync.link((text) => { o.watch('valueProp', {label: $$.textStoreValueProp}, watcher); @@ -180,6 +180,7 @@ export default class bTextarea extends iInputText { }) }) + // @ts-ignore (override) protected override textStore!: string; /** diff --git a/src/components/friends/module-loader/test/b-friends-module-loader-dummy/b-friends-module-loader-dummy.ts b/src/components/friends/module-loader/test/b-friends-module-loader-dummy/b-friends-module-loader-dummy.ts index 7de1b61ae5..94aa151806 100644 --- a/src/components/friends/module-loader/test/b-friends-module-loader-dummy/b-friends-module-loader-dummy.ts +++ b/src/components/friends/module-loader/test/b-friends-module-loader-dummy/b-friends-module-loader-dummy.ts @@ -40,5 +40,6 @@ export default class bFriendsModuleLoaderDummy extends iData { [] }) - override dependenciesProp!: Module[]; + // @ts-ignore (override) + override readonly dependenciesProp!: Module[]; } diff --git a/src/components/global/g-hint/g-hint.ts b/src/components/global/g-hint/g-hint.ts new file mode 100644 index 0000000000..8314502411 --- /dev/null +++ b/src/components/global/g-hint/g-hint.ts @@ -0,0 +1,9 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +export * from 'components/global/g-hint/interface'; diff --git a/src/components/super/i-block/event/README.md b/src/components/super/i-block/event/README.md index adab86caf6..aa53a363b5 100644 --- a/src/components/super/i-block/event/README.md +++ b/src/components/super/i-block/event/README.md @@ -979,7 +979,9 @@ import iBlock, { component } from 'components/super/i-block/i-block'; @component() export default class bExample extends iBlock { - override $refs!: {myInput: HTMLInputElement}; + declare protected readonly $refs: iBlock['$refs'] & { + myInput: HTMLInputElement + }; created() { this.waitRef('myInput').then((myInput) => { diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index 5b1fc42022..b656f8b184 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -505,7 +505,9 @@ export default abstract class iBlockEvent extends iBlockBase { * * @component() * export default class bExample extends iBlock { - * override $refs!: {myInput: HTMLInputElement}; + * declare protected readonly $refs: iBlock['$refs'] & { + * myInput: HTMLInputElement + * }; * * created() { * this.waitRef('myInput').then((myInput) => { diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index e5e7eb4ec0..cf8324053f 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -24,34 +24,32 @@ export * from 'components/super/i-block/modules/mods/interface'; * * @param component */ -export function initMods(component: iBlock): ModsDict { - const - ctx = component.unsafe, - declMods = ctx.meta.component.mods; +export function initMods(component: iBlock['unsafe']): ModsDict { + const declMods = component.meta.component.mods; const attrMods: Array<[string, () => CanUndef]> = [], modVal = (val: unknown) => val != null ? String(val) : undefined; - Object.keys(ctx.$attrs).forEach((attrName) => { + Object.keys(component.$attrs).forEach((attrName) => { const modName = attrName.camelize(false); if (modName in declMods) { let el: Nullable; - ctx.watch(`$attrs.${attrName}`, (attrs: Dictionary = {}) => { - el ??= ctx.$el; + component.watch(`$attrs.${attrName}`, (attrs: Dictionary = {}) => { + el ??= component.$el; if (el instanceof Element) { el.removeAttribute(attrName); } - void ctx.setMod(modName, modVal(attrs[attrName])); + void component.setMod(modName, modVal(attrs[attrName])); }); - ctx.meta.hooks['before:mounted'].push({ + component.meta.hooks['before:mounted'].push({ fn: () => { - el = ctx.$el; + el = component.$el; if (el instanceof Element) { el.removeAttribute(attrName); @@ -59,16 +57,16 @@ export function initMods(component: iBlock): ModsDict { } }); - attrMods.push([modName, () => modVal(ctx.$attrs[attrName])]); + attrMods.push([modName, () => modVal(component.$attrs[attrName])]); } }); - return Object.cast(ctx.sync.link(link)); + return Object.cast(component.sync.link(link)); function link(propMods: CanUndef): ModsDict { const - isModsInitialized = Object.isDictionary(ctx.mods), - mods = isModsInitialized ? ctx.mods : {...declMods}; + isModsInitialized = Object.isDictionary(component.mods), + mods = isModsInitialized ? component.mods : {...declMods}; if (propMods != null) { Object.entries(propMods).forEach(([key, val]) => { @@ -86,7 +84,7 @@ export function initMods(component: iBlock): ModsDict { } }); - const {experiments} = ctx.r.remoteState; + const {experiments} = component.r.remoteState; if (Object.isArray(experiments)) { experiments.forEach((exp) => { @@ -108,8 +106,8 @@ export function initMods(component: iBlock): ModsDict { val = modVal(mods[name]); mods[name] = val; - if (ctx.hook !== 'beforeDataCreate') { - void ctx.setMod(name, val); + if (component.hook !== 'beforeDataCreate') { + void component.setMod(name, val); } }); diff --git a/src/components/super/i-block/test/b-super-i-block-destructor-dummy/b-super-i-block-destructor-dummy.ts b/src/components/super/i-block/test/b-super-i-block-destructor-dummy/b-super-i-block-destructor-dummy.ts index 5753ab78eb..be6cad507c 100644 --- a/src/components/super/i-block/test/b-super-i-block-destructor-dummy/b-super-i-block-destructor-dummy.ts +++ b/src/components/super/i-block/test/b-super-i-block-destructor-dummy/b-super-i-block-destructor-dummy.ts @@ -15,16 +15,16 @@ export * from 'components/dummies/b-dummy/b-dummy'; @component() export default class bSuperIBlockDestructorDummy extends bDummy { - override $refs!: bDummy['$refs'] & { - child?: CanArray; - }; - @field() content: boolean = true; @system() store: iBlock[] = []; + declare protected readonly $refs: bDummy['$refs'] & { + child?: CanArray; + }; + pushToStore(component: iBlock): void { this.store.push(component); } diff --git a/src/components/super/i-block/test/unit/destructor.ts b/src/components/super/i-block/test/unit/destructor.ts index e74a3f5d48..5ba193fa0c 100644 --- a/src/components/super/i-block/test/unit/destructor.ts +++ b/src/components/super/i-block/test/unit/destructor.ts @@ -92,7 +92,7 @@ test.describe(' calling a component\'s destructor', () => { test('when it is called directly more than once', async ({page, consoleTracker}) => { const target = await renderDestructorDummy(page), - dynamicChild = await target.evaluateHandle((ctx) => ctx.$refs.child?.[1]), + dynamicChild = await target.evaluateHandle((ctx) => ctx.unsafe.$refs.child?.[1]), destructor = await dynamicChild.evaluateHandle((ctx) => ctx.unsafe.$destroy.bind(ctx)); await destructor.evaluate((destroy) => destroy(false)); @@ -112,7 +112,7 @@ test.describe(' calling a component\'s destructor', () => { async ({page, consoleTracker}) => { const target = await renderDestructorDummy(page), - dynamicChild = await target.evaluateHandle((ctx) => ctx.$refs.child?.[1]); + dynamicChild = await target.evaluateHandle((ctx) => ctx.unsafe.$refs.child?.[1]); await dynamicChild.evaluate((ctx) => { ctx.unsafe.$destroy({recursive: false}); diff --git a/src/components/super/i-data/i-data.ts b/src/components/super/i-data/i-data.ts index efbc2dd819..4ec1028fe1 100644 --- a/src/components/super/i-data/i-data.ts +++ b/src/components/super/i-data/i-data.ts @@ -89,8 +89,7 @@ export default abstract class iData extends iDataHandlers { this.syncDataProviderWatcher(false); } - const - providerHydrationKey = '[[DATA_PROVIDER]]'; + const providerHydrationKey = '[[DATA_PROVIDER]]'; const setDBData = (data: CanUndef) => { this.saveDataToRootStore(data); @@ -109,8 +108,7 @@ export default abstract class iData extends iDataHandlers { void this.db; }; - const - hydrationMode = this.canUseHydratedData && Boolean(this.field.get('ssrRendering')); + const hydrationMode = this.canUseHydratedData && Boolean(this.field.get('ssrRendering')); if (hydrationMode) { const @@ -131,8 +129,7 @@ export default abstract class iData extends iDataHandlers { ...opts }; - $a - .clearAll({group: 'requestSync:get'}); + $a.clearAll({group: 'requestSync:get'}); if (this.isFunctional && !SSR) { const res = super.initLoad(() => { @@ -150,8 +147,7 @@ export default abstract class iData extends iDataHandlers { return res; } - const - {dataProvider} = this; + const {dataProvider} = this; if (!opts.silent) { this.componentStatus = 'loading'; @@ -162,8 +158,7 @@ export default abstract class iData extends iDataHandlers { void this.lfc.execCbAtTheRightTime(() => this.db = db, label); } else if ((!SSR || this.ssrRendering) && dataProvider?.provider.baseURL != null) { - const - needRequest = Object.isArray(dataProvider.getDefaultRequestParams('get')); + const needRequest = Object.isArray(dataProvider.getDefaultRequestParams('get')); if (needRequest) { const res = $a @@ -193,8 +188,7 @@ export default abstract class iData extends iDataHandlers { void this.state.initFromStorage(); } - const - req = dataProvider.get(query, opts); + const req = dataProvider.get(query, opts); const timeout = $a.sleep(SSR ? 20 : (3).seconds()).then(() => { throw 'timeout'; diff --git a/src/components/super/i-data/interface.ts b/src/components/super/i-data/interface.ts index 4721806b1e..4fcb5910a8 100644 --- a/src/components/super/i-data/interface.ts +++ b/src/components/super/i-data/interface.ts @@ -6,29 +6,12 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type { RequestQuery, RequestBody, ModelMethod } from 'core/data'; -import type { CreateRequestOptions } from 'components/traits/i-data-provider/i-data-provider'; - import type { UnsafeIBlock } from 'components/super/i-block/i-block'; import type iData from 'components/super/i-data/i-data'; export * from 'components/friends/data-provider'; export * from 'components/traits/i-data-provider/i-data-provider'; -export interface RequestFilterOptions { - isEmpty: boolean; - method: ModelMethod; - params: CreateRequestOptions; -} - -export interface RequestFilterFn { - (data: RequestQuery | RequestBody, opts: RequestFilterOptions): boolean; -} - -export type RequestFilter = - boolean | - RequestFilterFn; - export interface RetryRequestFn { (): Promise>; } diff --git a/src/components/super/i-page/README.md b/src/components/super/i-page/README.md index 97bd2bbafa..0e2bdcfbce 100644 --- a/src/components/super/i-page/README.md +++ b/src/components/super/i-page/README.md @@ -47,20 +47,20 @@ Additionally, you can view the implemented traits or the parent component. #### [pageTitleProp] The current page title. -Basically this title is set via `document.title`. +Basically, this title is set via `document.title`. If the prop value is defined as a function, it will be called (the result will be used as the title). #### [pageDescriptionProp] The current page description. -Basically this description is set via ``. +Basically, this description is set via ``. If the prop value is defined as a function, it will be called (the result will be used as the description content). #### [stagePageTitles] -A dictionary of page titles (basically these titles are set via `document.title`). +A dictionary of page titles (basically, these titles are set via `document.title`). The dictionary values are bound to the `stage` values. The key named `[[DEFAULT]]` is used by default. If the key value is defined as a function, diff --git a/src/components/super/i-page/i-page.ts b/src/components/super/i-page/i-page.ts index adce23223a..5aefb13f36 100644 --- a/src/components/super/i-page/i-page.ts +++ b/src/components/super/i-page/i-page.ts @@ -29,8 +29,7 @@ import type { export * from 'components/super/i-data/i-data'; export * from 'components/super/i-page/interface'; -const - $$ = symbolGenerator(); +const $$ = symbolGenerator(); @component({inheritMods: false}) export default abstract class iPage extends iData implements iVisible { @@ -43,7 +42,7 @@ export default abstract class iPage extends iData implements iVisible { /** * The current page title. - * Basically this title is set via `document.title`. + * Basically, this title is set via `document.title`. * If the prop value is defined as a function, it will be called (the result will be used as the title). */ @prop({type: [String, Function]}) @@ -51,7 +50,7 @@ export default abstract class iPage extends iData implements iVisible { /** * The current page description. - * Basically this description is set via ``. + * Basically, this description is set via ``. * If the prop value is defined as a function, it will be called (the result will be used as the description content). */ @prop({type: [String, Function]}) @@ -80,7 +79,7 @@ export default abstract class iPage extends iData implements iVisible { /** * Sets a new page title. - * Basically this title is set via `document.title`. + * Basically, this title is set via `document.title`. * * @param value */ @@ -98,7 +97,7 @@ export default abstract class iPage extends iData implements iVisible { */ @computed({cache: true}) get scrollToProxy(): this['scrollTo'] { - return (...args) => { + return (...args: any[]) => { this.async.setImmediate(() => this.scrollTo(...args), { label: $$.scrollTo }); @@ -180,8 +179,7 @@ export default abstract class iPage extends iData implements iVisible { } if (this.stage != null) { - let - v = stageTitles[this.stage]; + let v = stageTitles[this.stage]; if (v == null) { v = stageTitles['[[DEFAULT]]']; diff --git a/src/components/super/i-static-page/i-static-page.ts b/src/components/super/i-static-page/i-static-page.ts index 9a1a077168..00f15cecf9 100644 --- a/src/components/super/i-static-page/i-static-page.ts +++ b/src/components/super/i-static-page/i-static-page.ts @@ -296,8 +296,7 @@ export default abstract class iStaticPage extends iPage { return false; } - const - normalizedValue = value !== undefined ? String(value).dasherize() : undefined; + const normalizedValue = value !== undefined ? String(value).dasherize() : undefined; if (normalizedValue === undefined || normalizedValue === cache.value) { root.classList.remove(cache.class); diff --git a/src/components/traits/i-active-items/i-active-items.ts b/src/components/traits/i-active-items/i-active-items.ts index 0b34349b9a..502ba1c517 100644 --- a/src/components/traits/i-active-items/i-active-items.ts +++ b/src/components/traits/i-active-items/i-active-items.ts @@ -168,7 +168,7 @@ export default abstract class iActiveItems extends iItems { * @param ctx */ static initActiveStoreListeners(ctx: TraitComponent): void { - ctx.unsafe.watch('activeStore', {deep: ctx.multiple}, (value) => { + (ctx).watch('activeStore', {deep: ctx.multiple}, (value) => { ctx.emit(ctx.activeChangeEvent, value); }); } diff --git a/src/components/traits/i-data-provider/i-data-provider.ts b/src/components/traits/i-data-provider/i-data-provider.ts index 3dfb1efa8a..8ebdad06a8 100644 --- a/src/components/traits/i-data-provider/i-data-provider.ts +++ b/src/components/traits/i-data-provider/i-data-provider.ts @@ -22,12 +22,11 @@ import type { DataProviderProp, DataProviderOptions } from 'components/friends/d import iProgress from 'components/traits/i-progress/i-progress'; import type iBlock from 'components/super/i-block/i-block'; -import type { RequestParams } from 'components/traits/i-data-provider/interface'; +import type { RequestParams, RequestFilter } from 'components/traits/i-data-provider/interface'; export * from 'components/traits/i-data-provider/interface'; -const - $$ = symbolGenerator(); +const $$ = symbolGenerator(); export default abstract class iDataProvider implements iProgress { /** @@ -80,6 +79,14 @@ export default abstract class iDataProvider implements iProgress { */ abstract readonly request?: RequestParams; + /** + * A function to filter all "default" requests: all requests that were created implicitly, as the initial + * request of a component, or requests that are initiated by changing parameters from `request` and `requestParams`. + * If the filter returns negative value, the tied request will be aborted. + * You can also set this parameter to true, and it will only pass requests with a payload. + */ + abstract readonly defaultRequestFilter?: RequestFilter; + /** * If true, all requests to the data provider are suspended till you manually resolve them. * This option is used when you want to lazy load components. For instance, you can only load components in diff --git a/src/components/traits/i-data-provider/interface.ts b/src/components/traits/i-data-provider/interface.ts index e8e189da65..30f88b595f 100644 --- a/src/components/traits/i-data-provider/interface.ts +++ b/src/components/traits/i-data-provider/interface.ts @@ -31,3 +31,17 @@ export interface CreateRequestOptions extends BaseCreateRequestOpti showProgress?: boolean; hideProgress?: boolean; } + +export interface RequestFilterOptions { + isEmpty: boolean; + method: ModelMethod; + params: CreateRequestOptions; +} + +export interface RequestFilterFn { + (data: RequestQuery | RequestBody, opts: RequestFilterOptions): boolean; +} + +export type RequestFilter = + boolean | + RequestFilterFn; diff --git a/src/core/component/decorators/interface/field.ts b/src/core/component/decorators/interface/field.ts index 99b45727ca..993dc7625d 100644 --- a/src/core/component/decorators/interface/field.ts +++ b/src/core/component/decorators/interface/field.ts @@ -252,9 +252,9 @@ export interface InitFieldFn { - (ctx: CTX['unsafe'], oldCtx: CTX, field: string, link?: string): unknown; + (ctx: CTX['unsafe'], oldCtx: CTX['unsafe'], field: string, link?: string): unknown; } export interface UniqueFieldFn { - (ctx: CTX['unsafe'], oldCtx: CTX): AnyToBoolean; + (ctx: CTX['unsafe'], oldCtx: CTX['unsafe']): AnyToBoolean; } diff --git a/src/core/component/directives/ref/index.ts b/src/core/component/directives/ref/index.ts index 9a7c4be7da..e51db49b8c 100644 --- a/src/core/component/directives/ref/index.ts +++ b/src/core/component/directives/ref/index.ts @@ -40,7 +40,7 @@ function updateRef(el: Element | ComponentElement, opts: DirectiveOptions, vnode } = opts; let ctx = getDirectiveContext(opts, vnode); - ctx = Object.cast(ctx?.unsafe.meta.params.functional === true ? ctx : instance); + ctx = Object.cast(ctx?.meta.params.functional === true ? ctx : instance); if ( value == null || diff --git a/src/core/component/functional/helpers.ts b/src/core/component/functional/helpers.ts index 935976d6fe..a28dc9030d 100644 --- a/src/core/component/functional/helpers.ts +++ b/src/core/component/functional/helpers.ts @@ -46,7 +46,8 @@ export function initDynamicComponentLifeCycle(component: ComponentInterface): Co } function mount() { - unsafe.unsafe.$el = node; + // @ts-ignore (unsafe) + unsafe.$el = node; node.component = unsafe; // Performs a mount on the next tick to ensure that the component is rendered @@ -77,7 +78,7 @@ export function inheritContext( // Here, the functional component is recreated during re-rendering. // Therefore, the destructor call should not recursively propagate to child components. // Also, we should not unmount the vnodes created within the component. - parentCtx.unsafe.$destroy({recursive: false, shouldUnmountVNodes: false}); + parentCtx.$destroy({recursive: false, shouldUnmountVNodes: false}); const props = ctx.$props, diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 9900969e3c..cc8f31f392 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -48,6 +48,7 @@ export function beforeCreateState( // To avoid TS errors marks all properties as editable const unsafe = Object.cast>(component); + // @ts-ignore (unsafe) unsafe.unsafe = unsafe; unsafe.componentName = meta.componentName; unsafe.meta = meta; @@ -62,8 +63,6 @@ export function beforeCreateState( unsafe.$systemFields = {}; unsafe.$modifiedFields = {}; unsafe.$renderCounter = 0; - - // A stub for the correct functioning of $parent unsafe.$restArgs = undefined; unsafe.async = new Async(component); diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index ecca4e267f..52edd75e4a 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -279,6 +279,11 @@ export abstract class ComponentInterface { */ protected readonly $syncLinkCache!: SyncLinkCache; + /** + * A stub for the correct functioning of `$parent` + */ + protected $restArgs!: unknown; + /** * An API for binding and managing asynchronous operations */ diff --git a/src/core/component/interface/component/unsafe.ts b/src/core/component/interface/component/unsafe.ts index eff947ce80..eb368bc6ac 100644 --- a/src/core/component/interface/component/unsafe.ts +++ b/src/core/component/interface/component/unsafe.ts @@ -90,6 +90,9 @@ export interface UnsafeComponentInterface Date: Tue, 20 Aug 2024 15:03:08 +0300 Subject: [PATCH 043/334] chore: fixed TS errors --- .../b-virtual-scroll-new/test/unit/functional/props/props.ts | 2 +- .../test/unit/functional/rendering/default.ts | 2 +- .../base/b-virtual-scroll-new/test/unit/scenario/reload.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts index 5103e39a60..42e6ae3fd0 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts @@ -55,7 +55,7 @@ test.describe('', () => { await component.waitForChildCountEqualsTo(chunkSize * 3); const - produceSpy = await component.getSpy((ctx) => ctx.componentFactory.produceComponentItems); + produceSpy = await component.getSpy((ctx) => ctx.unsafe.componentFactory.produceComponentItems); test.expect(provider.mock.mock.calls.length).toBe(3); await test.expect(produceSpy.calls).resolves.toHaveLength(2); diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/rendering/default.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/rendering/default.ts index 6dbe86e274..74503bdd73 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/rendering/default.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/rendering/default.ts @@ -159,7 +159,7 @@ test.describe('', () => { }); const - spy = await component.getSpy((ctx) => ctx.componentFactory.produceNodes); + spy = await component.getSpy((ctx) => ctx.unsafe.componentFactory.produceNodes); await test.expect(spy.callsCount).resolves.toBe(7); await test.expect(component.childList).toHaveCount(providerChunkSize); diff --git a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts index 6ab72117cf..89ee723ba7 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts @@ -115,7 +115,7 @@ test.describe('', () => { .build(); await component.waitForDataIndexChild(chunkSize - 1); - await component.evaluate((ctx, [event]) => ctx.globalEmitter.emit(event), [event]); + await component.evaluate((ctx, [event]) => ctx.unsafe.globalEmitter.emit(event), [event]); await component.waitForDataIndexChild(chunkSize * 2 - 1); const From 760523c61eb714fa39f3aa2eec8d663051a4c0e1 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 20 Aug 2024 16:50:25 +0300 Subject: [PATCH 044/334] chore: optimization --- src/components/friends/field/README.md | 14 ++--- src/components/friends/field/delete.ts | 42 +++++++-------- src/components/friends/field/get.ts | 45 ++++++++-------- src/components/friends/field/set.ts | 74 ++++++++++++++------------ 4 files changed, 91 insertions(+), 84 deletions(-) diff --git a/src/components/friends/field/README.md b/src/components/friends/field/README.md index 6bbb1e6c66..60dd756a64 100644 --- a/src/components/friends/field/README.md +++ b/src/components/friends/field/README.md @@ -20,7 +20,7 @@ this.field.delete('foo.bla.bar', this.r); By default, any component that inherits from [[iBlock]] has the `field` property. Some methods, such as `get` and `set`, are always available, while others need to be explicitly included to enable tree-shaking code optimization. -To do this, simply add the necessary import declaration within your component file. +To do this, add the necessary import declaration within your component file. ```typescript import iBlock, { component } from 'components/super/i-block/i-block'; @@ -74,7 +74,7 @@ There are three reasons to use `Field` instead of Prelude methods. ``` 2. Prelude methods are not aware of component states and hooks. - For example, before the component switches to the "created" hook, + For example, before the component switches to the `created` hook, we cannot directly set the field values because all fields are initialized during `created`. In this case, the `Field` class can optionally set the value to the internal store. @@ -135,8 +135,8 @@ There are three reasons to use `Field` instead of Prelude methods. ### get -Returns a property based on the specified path. -This method is plugged in by default. +Returns a property from the provided object or the source component at the specified path. +This method is enabled by default. ```typescript import iBlock, { component, field } from 'components/super/i-block/i-block'; @@ -167,8 +167,8 @@ export default class bInput extends iBlock { ### set -Sets a new property based on the specified path. -This method is plugged in by default. +Sets a new property on the provided object or the source component at the specified path. +This method is enabled by default. ```typescript import iBlock, { component, field } from 'components/super/i-block/i-block'; @@ -198,7 +198,7 @@ export default class bInput extends iBlock { ### delete -Deletes a property based on the specified path. +Deletes a property from the provided object or the source component at the specified path. ```typescript import iBlock, { component, field } from 'components/super/i-block/i-block'; diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index 57b8b122a6..c8a0be523d 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -14,7 +14,7 @@ import iBlock, { getPropertyInfo } from 'components/super/i-block/i-block'; import type { KeyGetter } from 'components/friends/field/interface'; /** - * Deletes a component property by the specified path + * Deletes a component property at the specified path * * @param path - the property path, for instance `foo.bla.bar` * @param keyGetter - a function that returns the key to delete @@ -44,7 +44,7 @@ import type { KeyGetter } from 'components/friends/field/interface'; export function deleteField(this: Friend, path: string, keyGetter?: KeyGetter): boolean; /** - * Deletes a property from the passed object by the specified path + * Deletes a property from the passed object at the specified path * * @param path - the property path, for instance `foo.bla.bar` * @param [obj] - the object to delete the property @@ -86,11 +86,9 @@ export function deleteField( return false; } - let - {ctx} = this; + let {ctx} = this; - let - isComponent = false; + let isComponent = false; if ((obj).instance instanceof iBlock) { ctx = (obj).unsafe; @@ -98,12 +96,12 @@ export function deleteField( } let - sync, + sync: CanNull<() => boolean> = null, needDeleteToWatch = isComponent; let ref = obj, - chunks; + chunks: string[]; if (isComponent) { const info = getPropertyInfo(path, Object.cast(ctx)); @@ -115,7 +113,7 @@ export function deleteField( ctx = Object.cast(info.ctx); - chunks = info.path.split('.'); + chunks = info.path.includes('.') ? info.path.split('.') : [info.path]; chunks[0] = info.name; if (isSystem || isField) { @@ -134,7 +132,7 @@ export function deleteField( // Otherwise, we must synchronize these properties between the proxy object and the component instance } else { const name = chunks[0]; - sync = () => Object.delete(ctx.$systemFields, [name]); + sync = () => delete ctx.$systemFields[name]; } } else if (ctx.isFunctionalWatchers) { @@ -144,7 +142,7 @@ export function deleteField( // we must synchronize these properties between the proxy object and the component instance if (unwrap(ref) === ref) { const name = chunks[0]; - sync = () => Object.delete(ctx, [name]); + sync = () => delete ctx[name]; } } else { @@ -153,37 +151,39 @@ export function deleteField( } } else { - chunks = path.split('.'); + chunks = path.includes('.') ? path.split('.') : [path]; } let needDelete = true, - prop; + prop = keyGetter ? keyGetter(chunks[0], ref) : chunks[0]; - for (let i = 0; i < chunks.length; i++) { - prop = keyGetter ? keyGetter(chunks[i], ref) : chunks[i]; + chunks.some((key, i) => { + prop = keyGetter ? keyGetter(key, ref) : key; if (i + 1 === chunks.length) { - break; + return true; } - const - newRef = Object.get(ref, [prop]); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const newRef = ref != null ? ref[prop] : undefined; if (newRef == null || typeof newRef !== 'object') { needDelete = false; - break; + return true; } ref = newRef; - } + return false; + }); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (needDelete) { if (needDeleteToWatch) { ctx.$delete(ref, prop); } else { - Object.delete(ref, [prop]); + delete ref[prop]; } if (sync != null) { diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index bf1967e3c0..bd1b99f008 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -12,10 +12,10 @@ import iBlock, { getPropertyInfo } from 'components/super/i-block/i-block'; import type { ValueGetter } from 'components/friends/field/interface'; /** - * Returns a component property by the specified path + * Returns a component property at the specified path * * @param path - the property path, for instance `foo.bla.bar` - * @param getter - a function that is used to get a value from an object and a property + * @param getter - a function used to get a value from an object and a property * * @example * ```typescript @@ -42,7 +42,7 @@ import type { ValueGetter } from 'components/friends/field/interface'; export function getField(this: Friend, path: string, getter: ValueGetter): CanUndef; /** - * Returns a property from the passed object by the specified path + * Returns a property from the passed object at the specified path * * @param path - the property path, for instance `foo.bla.bar` * @param [obj] - the object to search @@ -79,11 +79,9 @@ export function getField( return; } - let - {ctx} = this; + let {ctx} = this; - let - isComponent = false; + let isComponent = false; if ((obj).instance instanceof iBlock) { ctx = (obj).unsafe; @@ -92,7 +90,7 @@ export function getField( let res: unknown = obj, - chunks; + chunks: string[]; if (isComponent) { const info = getPropertyInfo(path, Object.cast(ctx)); @@ -100,7 +98,7 @@ export function getField( ctx = Object.cast(info.ctx); res = ctx; - chunks = info.path.split('.'); + chunks = info.path.includes('.') ? info.path.split('.') : [info.path]; if (info.accessor != null) { chunks[0] = info.accessor; @@ -124,33 +122,38 @@ export function getField( } } else { - chunks = path.split('.'); + chunks = path.includes('.') ? path.split('.') : [path]; } - if (getter == null) { - res = Object.get(res, chunks); + if (chunks.length === 1) { + res = getter != null ? getter(chunks[0], res) : (res)[chunks[0]]; } else { - for (let i = 0; i < chunks.length; i++) { + const hasNotProperty = chunks.some((key) => { if (res == null) { - return undefined; + return true; } - const - key = chunks[i]; - if (Object.isPromiseLike(res) && !(key in res)) { - res = res.then((res) => getter!(key, res)); + res = res.then((res) => getter != null ? getter(key, res) : (res)[key]); } else { - res = getter(key, res); + res = getter != null ? getter(key, res) : (res)[key]; } + + return false; + }); + + if (hasNotProperty) { + return undefined; } } if (Object.isPromiseLike(res)) { - return Object.cast(this.async.promise(res)); + // @ts-ignore (cast) + return this.async.promise(res); } - return Object.cast(res); + // @ts-ignore (cast) + return res; } diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index d07bd30f7f..414d31e882 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -14,10 +14,10 @@ import iBlock, { getPropertyInfo } from 'components/super/i-block/i-block'; import type { KeyGetter } from 'components/friends/field/interface'; /** - * Sets a new component property by the specified path + * Sets a new component property at the specified path * * @param path - the property path, for instance `foo.bla.bar` - * @param value - a value to set to the property + * @param value - the value to set to the property * @param keyGetter - a function that returns the key to set * * @example @@ -42,10 +42,10 @@ import type { KeyGetter } from 'components/friends/field/interface'; export function setField(this: Friend, path: string, value: T, keyGetter: KeyGetter): T; /** - * Sets a new property to the passed object by the specified path + * Sets a new property on the passed object at the specified path * * @param path - the property path, for instance `foo.bla.bar` - * @param value - a value to set to the property + * @param value - the value to set to the property * @param [obj] - the object to set the property * @param [keyGetter] - a function that returns the key to set * @@ -84,11 +84,9 @@ export function setField( return value; } - let - {ctx} = this; + let {ctx} = this; - let - isComponent = false; + let isComponent = false; if ((obj).instance instanceof iBlock) { ctx = (obj).unsafe; @@ -96,12 +94,12 @@ export function setField( } let - sync, + sync: CanNull<() => T> = null, needSetToWatch = isComponent; let ref = obj, - chunks; + chunks: string[]; if (isComponent) { const info = getPropertyInfo(path, Object.cast(ctx)); @@ -109,15 +107,14 @@ export function setField( ctx = Object.cast(info.ctx); ref = ctx; - chunks = info.path.split('.'); + chunks = info.path.includes('.') ? info.path.split('.') : [info.path]; if (info.accessor != null) { needSetToWatch = false; chunks[0] = info.accessor; } else { - const - isReady = !ctx.lfc.isBeforeCreate(); + const isReady = !ctx.lfc.isBeforeCreate(); const isSystem = info.type === 'system', @@ -137,7 +134,7 @@ export function setField( // Otherwise, we must synchronize these properties between the proxy object and the component instance } else { const name = chunks[0]; - sync = () => Object.set(ctx.$systemFields, [name], ref[name]); + sync = () => ctx.$systemFields[name] = ref[name]; } } else if (ctx.isFunctionalWatchers) { @@ -147,7 +144,7 @@ export function setField( // we must synchronize these properties between the proxy object and the component instance if (unwrap(ref) === ref) { const name = chunks[0]; - sync = () => Object.set(ctx, [name], ref[name]); + sync = () => ctx[name] = ref[name]; } } else { @@ -157,38 +154,45 @@ export function setField( } } else { - chunks = path.split('.'); + chunks = path.includes('.') ? path.split('.') : [path]; } - let - prop; + let prop = keyGetter ? keyGetter(chunks[0], ref) : chunks[0]; - for (let i = 0; i < chunks.length; i++) { - prop = keyGetter ? keyGetter(chunks[i], ref) : chunks[i]; + if (chunks.length > 1) { + chunks.some((key, i) => { + prop = keyGetter ? keyGetter(key, ref) : key; - if (i + 1 === chunks.length) { - break; - } + if (i + 1 === chunks.length) { + return true; + } - let - newRef = Object.get(ref, [prop]); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + let newRef = ref != null ? ref[prop] : undefined; - if (newRef == null || typeof newRef !== 'object') { - newRef = isNaN(Number(chunks[i + 1])) ? {} : []; + if (newRef == null || typeof newRef !== 'object') { + newRef = isNaN(Number(chunks[i + 1])) ? {} : []; - if (needSetToWatch) { - ctx.$set(ref, prop, newRef); + if (needSetToWatch) { + ctx.$set(ref, prop, newRef); - } else { - Object.set(ref, [prop], newRef); + } else { + ref[prop] = newRef; + } } - } - ref = Object.get(ref, [prop])!; + ref = newRef; + return false; + }); + } + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (ref == null) { + return value; } - if (!needSetToWatch || !Object.isArray(ref) && Object.has(ref, [prop])) { - Object.set(ref, [prop], value); + if (!needSetToWatch || !Object.isArray(ref) && prop in ref) { + ref[prop] = value; } else { ctx.$set(ref, prop, value); From 0dc6c385ba32eb30f025c81f858728e58fa13fea Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 20 Aug 2024 17:57:01 +0300 Subject: [PATCH 045/334] chore: fixes aster refactoring --- src/components/friends/field/delete.ts | 30 ++++++++++++++------------ src/components/friends/field/get.ts | 4 ++-- src/components/friends/field/set.ts | 2 +- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index c8a0be523d..4325d452f3 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -158,24 +158,26 @@ export function deleteField( needDelete = true, prop = keyGetter ? keyGetter(chunks[0], ref) : chunks[0]; - chunks.some((key, i) => { - prop = keyGetter ? keyGetter(key, ref) : key; + if (chunks.length > 1) { + chunks.some((key, i) => { + prop = keyGetter ? keyGetter(key, ref) : key; - if (i + 1 === chunks.length) { - return true; - } + if (i + 1 === chunks.length) { + return true; + } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const newRef = ref != null ? ref[prop] : undefined; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const newRef = ref != null ? ref[prop] : undefined; - if (newRef == null || typeof newRef !== 'object') { - needDelete = false; - return true; - } + if (newRef == null || typeof newRef !== 'object') { + needDelete = false; + return true; + } - ref = newRef; - return false; - }); + ref = newRef; + return false; + }); + } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (needDelete) { diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index bd1b99f008..e84f53f8c8 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -129,7 +129,7 @@ export function getField( res = getter != null ? getter(chunks[0], res) : (res)[chunks[0]]; } else { - const hasNotProperty = chunks.some((key) => { + const hasNoProperty = chunks.some((key) => { if (res == null) { return true; } @@ -144,7 +144,7 @@ export function getField( return false; }); - if (hasNotProperty) { + if (hasNoProperty) { return undefined; } } diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index 414d31e882..5fe360888b 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -181,7 +181,7 @@ export function setField( } } - ref = newRef; + ref = ref[prop]; return false; }); } From 43e425e145a2dd357c5077080814ddc390142115 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 21 Aug 2024 15:03:32 +0300 Subject: [PATCH 046/334] chore: optimization & refactoring --- src/components/friends/sync/class.ts | 7 +- src/components/friends/sync/link.ts | 189 ++++++++++++++------------ src/components/friends/sync/mod.ts | 73 +++++----- src/components/friends/sync/object.ts | 3 +- src/components/friends/sync/sync.ts | 33 +++-- 5 files changed, 159 insertions(+), 146 deletions(-) diff --git a/src/components/friends/sync/class.ts b/src/components/friends/sync/class.ts index aec6babf2f..504d3248cd 100644 --- a/src/components/friends/sync/class.ts +++ b/src/components/friends/sync/class.ts @@ -66,6 +66,11 @@ class Sync extends Friend { */ protected readonly linksCache!: Dictionary; + /** + * The index of the last added link + */ + protected lastSyncIndex: number = 0; + /** {@link iBlock.$syncLinkCache} */ protected get syncLinkCache(): SyncLinkCache { return this.ctx.$syncLinkCache; @@ -73,7 +78,7 @@ class Sync extends Friend { /** {@link iBlock.$syncLinkCache} */ protected set syncLinkCache(value: SyncLinkCache) { - Object.set(this.ctx, '$syncLinkCache', value); + this.ctx.$syncLinkCache = value; } constructor(component: iBlock) { diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 14ca4a7b6b..1735a36276 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -7,7 +7,7 @@ */ import { isProxy } from 'core/object/watch'; -import { getPropertyInfo, isBinding, customWatcherRgxp } from 'core/component'; +import { getPropertyInfo, isBinding, isCustomWatcher } from 'core/component'; import type iBlock from 'components/super/i-block/i-block'; import type Sync from 'components/friends/sync/class'; @@ -18,12 +18,16 @@ import type { LinkDecl, LinkGetter, AsyncWatchOptions } from 'components/friends /** * Sets a reference to a property that is logically connected to the current field. * - * Simply put, if field A refers to field B, then it has the same value and will automatically update when B changes. - * You can refer to a value as a whole or to a part of it. Just pass a special getter function that will take - * parameters from the link and return the value to the original field. + * For example, if field A refers to field B, + * then it will have the same value and will automatically update when B changes. + * If the link is set to an event, every time this event fires, + * the value of A will change to the value of the event object. * - * Logical connection is based on a name convention: properties that match the patterns - * "${property} -> ${property}Prop" or "${property}Store -> ${property}Prop" are connected with each other. + * You can refer to a value as a whole or to a part of it. + * Pass a special getter function that will take parameters from the link and return the value to the original field. + * + * Logical connection is based on a naming convention: properties that match the patterns + * "${property} → ${property}Prop" or "${property}Store → ${property}Prop" are connected with each other. * * Mind, this function can be used only within a property decorator. * @@ -55,14 +59,18 @@ export function link( ): CanUndef; /** - * Sets a reference to a property that is logically connected to the current field. + * Sets a link to a property that is logically connected to the current field. + * + * For example, if field A refers to field B, + * then it will have the same value and will automatically update when B changes. + * If the link is set to an event, every time this event fires, + * the value of A will change to the value of the event object. * - * Simply put, if field A refers to field B, then it has the same value and will automatically update when B changes. - * You can refer to a value as a whole or to a part of it. Just pass a special getter function that will take - * parameters from the link and return the value to the original field. + * You can refer to a value as a whole or to a part of it. + * Pass a special getter function that will take parameters from the link and return the value to the original field. * - * Logical connection is based on a name convention: properties that match the patterns - * "${property} -> ${property}Prop" or "${property}Store -> ${property}Prop" are connected with each other. + * Logical connection is based on a naming convention: properties that match the patterns + * "${property} → ${property}Prop" or "${property}Store → ${property}Prop" are connected with each other. * * Mind, this method can be used only within a property decorator. * @@ -92,13 +100,16 @@ export function link( /** * Sets a link to a component/object property or event by the specified path. * - * Simply put, if field A refers to field B, then it has the same value and will automatically update when B changes. - * If the link is set to an event, then every time this event fires, then the value of A will change to the value of - * the event object. You can refer to a value as a whole or to a part of it. Just pass a special getter function - * that will take parameters from the link and return the value to the original field. + * For example, if field A refers to field B, + * then it will have the same value and will automatically update when B changes. + * If the link is set to an event, every time this event fires, + * the value of A will change to the value of the event object. * - * To listen an event you need to use the special delimiter ":" within a path. - * Also, you can specify an event emitter to listen by writing a link before ":". + * You can refer to a value as a whole or to a part of it. + * Pass a special getter function that will take parameters from the link and return the value to the original field. + * + * To listen to an event, you need to use the special delimiter ":" within a path. + * Additionally, you can specify an event emitter to listen to by writing a link before the ":" delimiter. * * {@link iBlock.watch} * @@ -161,15 +172,18 @@ export function link( ): CanUndef; /** - * Sets a link to a component/object property or event by the specified path. + * Sets a link to a component/object property or event at the specified path. + * + * For example, if field A refers to field B, + * then it will have the same value and will automatically update when B changes. + * If the link is set to an event, every time this event fires, + * the value of A will change to the value of the event object. * - * Simply put, if field A refers to field B, then it has the same value and will automatically update when B changes. - * If the link is set to an event, then every time this event fires, then the value of A will change to the value of - * the event object. You can refer to a value as a whole or to a part of it. Just pass a special getter function - * that will take parameters from the link and return the value to the original field. + * You can refer to a value as a whole or to a part of it. + * Pass a special getter function that will take parameters from the link and return the value to the original field. * - * To listen an event you need to use the special delimiter ":" within a path. - * Also, you can specify an event emitter to listen by writing a link before ":". + * To listen to an event, you need to use the special delimiter ":" within a path. + * Additionally, you can specify an event emitter to listen to by writing a link before the ":" delimiter. * * {@link iBlock.watch} * @@ -240,7 +254,7 @@ export function link( getter?: LinkGetter ): CanUndef { let - destPath, + destPath: CanUndef, resolvedPath: CanUndef; if (Object.isArray(path)) { @@ -275,8 +289,7 @@ export function link( return; } - let - resolvedOpts: AsyncWatchOptions = {}; + let resolvedOpts: AsyncWatchOptions = {}; if (path == null) { resolvedPath = `${isBinding.test(destPath) ? isBinding.replace(destPath) : destPath}Prop`; @@ -302,37 +315,37 @@ export function link( topPathIndex = 1; let - isMountedWatcher = false, - isCustomWatcher = false; - - if (!Object.isString(resolvedPath)) { - isMountedWatcher = true; - - if (isProxy(resolvedPath)) { - info = {ctx: resolvedPath}; - normalizedPath = undefined; + mountedWatcher = false, + customWatcher = false; - } else { - info = resolvedPath; - normalizedPath = info.path; - topPathIndex = 0; - } - - } else { + if (Object.isString(resolvedPath)) { normalizedPath = resolvedPath; - if (RegExp.test(customWatcherRgxp, normalizedPath)) { - isCustomWatcher = true; + if (isCustomWatcher.test(normalizedPath)) { + customWatcher = true; } else { info = getPropertyInfo(normalizedPath, this.component); if (info.type === 'mounted') { - isMountedWatcher = true; + mountedWatcher = true; normalizedPath = info.path; topPathIndex = Object.size(info.path) > 0 ? 0 : 1; } } + + } else { + mountedWatcher = true; + + if (isProxy(resolvedPath)) { + info = {ctx: resolvedPath}; + normalizedPath = undefined; + + } else { + info = resolvedPath; + normalizedPath = info.path; + topPathIndex = 0; + } } const isAccessor = info != null ? @@ -343,7 +356,7 @@ export function link( resolvedOpts.immediate = resolvedOpts.immediate !== false; } - if (!isCustomWatcher) { + if (!customWatcher) { if ( normalizedPath != null && ( Object.isArray(normalizedPath) && normalizedPath.length > topPathIndex || @@ -362,21 +375,27 @@ export function link( linksCache[destPath] = {}; - const sync = (val?, oldVal?) => { - const res = getter ? getter.call(this.component, val, oldVal) : val; - this.field.set(destPath, res); + const sync = (val?: unknown, oldVal?: unknown, src?: object) => { + const res = getter != null ? getter.call(this.component, val, oldVal) : val; + + if (src != null) { + src[destPath!] = res; + + } else { + this.field.set(destPath!, res); + } + return res; }; if (getter != null && (getter.length > 1 || getter['originalLength'] > 1)) { - ctx.watch(info ?? normalizedPath, resolvedOpts, (val, oldVal, ...args) => { - if (isCustomWatcher) { + ctx.watch(info ?? normalizedPath, resolvedOpts, (val: unknown, oldVal: unknown, ...args: unknown[]) => { + if (customWatcher) { oldVal = undefined; } else { if (args.length === 0 && Object.isArray(val) && val.length > 0) { - const - mutation = <[unknown, unknown]>val[val.length - 1]; + const mutation = <[unknown, unknown]>val[val.length - 1]; val = mutation[0]; oldVal = mutation[1]; @@ -391,11 +410,11 @@ export function link( }); } else { - ctx.watch(info ?? normalizedPath, resolvedOpts, (val, ...args) => { + ctx.watch(info ?? normalizedPath, resolvedOpts, (val: unknown, ...args: unknown[]) => { let oldVal: unknown = undefined; - if (!isCustomWatcher) { + if (!customWatcher) { if (args.length === 0 && Object.isArray(val) && val.length > 0) { const mutation = <[unknown, unknown]>val[val.length - 1]; @@ -417,10 +436,9 @@ export function link( } { - let - key; + let key: Nullable; - if (isMountedWatcher) { + if (mountedWatcher) { const o = info?.originalPath; key = Object.isString(o) ? o : info?.ctx ?? normalizedPath; @@ -428,51 +446,44 @@ export function link( key = normalizedPath; } - syncLinkCache.set(key, Object.assign(syncLinkCache.get(key) ?? {}, { - [destPath]: { - path: destPath, - sync - } - })); + if (key != null) { + syncLinkCache.set(key, Object.assign(syncLinkCache.get(key) ?? {}, { + [destPath]: { + path: destPath, + sync + } + })); + } } - if (isCustomWatcher) { + if (customWatcher) { return sync(); } const needCollapse = resolvedOpts.collapse !== false; - if (isMountedWatcher) { - const - obj = info?.ctx; + if (mountedWatcher) { + const obj = info?.ctx; - if (needCollapse || Object.size(normalizedPath) === 0) { + if (needCollapse || normalizedPath == null || normalizedPath.length === 0) { return sync(obj); } - return sync(Object.get(obj, normalizedPath!)); + return sync(Object.get(obj, normalizedPath)); } - const initSync = () => sync( - this.field.get(needCollapse ? info.originalTopPath : info.originalPath) - ); - - if (this.lfc.isBeforeCreate('beforeDataCreate')) { - const - name = '[[SYNC]]', - hooks = meta.hooks.beforeDataCreate; - - let - pos = 0; + const initSync = () => { + const src = + info.type === 'field' && (this.hook === 'beforeDataCreate' || ctx.isFunctionalWatchers) ? + ctx.$fields : + ctx; - hooks.forEach((hook, i) => { - if (hook.name === name) { - pos = i + 1; - } - }); + return sync(src[needCollapse ? info.originalTopPath : info.originalPath], undefined, src); + }; - hooks.splice(pos, 0, {fn: initSync, name}); + if (this.lfc.isBeforeCreate('beforeDataCreate')) { + meta.hooks.beforeDataCreate.splice(this.lastSyncIndex++, 0, {fn: initSync}); return; } diff --git a/src/components/friends/sync/mod.ts b/src/components/friends/sync/mod.ts index 23b1963927..d9c2795a74 100644 --- a/src/components/friends/sync/mod.ts +++ b/src/components/friends/sync/mod.ts @@ -12,11 +12,11 @@ import type Sync from 'components/friends/sync/class'; import type { ModValueConverter, LinkGetter, AsyncWatchOptions } from 'components/friends/sync/interface'; /** - * Binds a modifier to a property by the specified path + * Binds a modifier to a property at the specified path * - * @param modName - the modifier name to bind - * @param path - the property path to bind - * @param [converter] - a converter function + * @param modName - the name of the modifier to bind + * @param path - the path of the property to bind + * @param [converter] - an optional converter function * * @example * ```typescript @@ -44,12 +44,12 @@ export function mod( ): void; /** - * Binds a modifier to a property by the specified path + * Binds a modifier to a property at the specified path * - * @param modName - the modifier name to bind - * @param path - the property path to bind + * @param modName - the name of the modifier to bind + * @param path - the path of the property to bind * @param opts - additional options - * @param [converter] - converter function + * @param [converter] - an optional converter function * * @example * ```typescript @@ -86,8 +86,7 @@ export function mod( ): void { modName = modName.camelize(false); - let - opts: AsyncWatchOptions; + let opts: AsyncWatchOptions; if (Object.isFunction(optsOrConverter)) { converter = optsOrConverter; @@ -96,36 +95,11 @@ export function mod( opts = Object.cast(optsOrConverter); } - const - {ctx} = this; + const that = this; - const setWatcher = () => { - const wrapper = (val: unknown, ...args: unknown[]) => { - val = (converter).call(this.component, val, ...args); - - if (val !== undefined) { - void this.ctx.setMod(modName, val); - } - }; - - if (converter.length > 1) { - ctx.watch(path, opts, (val: unknown, oldVal: unknown) => wrapper(val, oldVal)); - - } else { - ctx.watch(path, opts, wrapper); - } - }; + const {ctx} = this; if (this.lfc.isBeforeCreate()) { - const sync = () => { - const - v = (converter).call(this.component, this.field.get(path)); - - if (v !== undefined) { - ctx.mods[modName] = String(v); - } - }; - this.syncModCache[modName] = sync; if (ctx.hook !== 'beforeDataCreate') { @@ -142,4 +116,29 @@ export function mod( } else if (statuses[ctx.componentStatus] >= 1) { setWatcher(); } + + function sync() { + const v = (converter).call(that.component, that.field.get(path)); + + if (v !== undefined) { + ctx.mods[modName] = String(v); + } + } + + function setWatcher() { + if (converter.length > 1) { + ctx.watch(path, opts, (val: unknown, oldVal: unknown) => wrapper(val, oldVal)); + + } else { + ctx.watch(path, opts, wrapper); + } + + function wrapper(val: unknown, ...args: unknown[]) { + val = (converter).call(that.component, val, ...args); + + if (val !== undefined) { + void ctx.setMod(modName, val); + } + } + } } diff --git a/src/components/friends/sync/object.ts b/src/components/friends/sync/object.ts index e2a74f25db..cd36b195e8 100644 --- a/src/components/friends/sync/object.ts +++ b/src/components/friends/sync/object.ts @@ -394,8 +394,7 @@ export function object( } { - let - key; + let key; if (isMountedWatcher) { const o = info?.originalPath; diff --git a/src/components/friends/sync/sync.ts b/src/components/friends/sync/sync.ts index 052ca0cfb7..b818c863af 100644 --- a/src/components/friends/sync/sync.ts +++ b/src/components/friends/sync/sync.ts @@ -7,10 +7,11 @@ */ import type Sync from 'components/friends/sync/class'; -import type { LinkDecl } from 'components/friends/sync/interface'; + +import type { LinkDecl, ObjectLink } from 'components/friends/sync/interface'; /** - * Synchronizes component reference values with the values they are linked with + * Synchronizes component link values with the values they are linked with * * @param [path] - a path to the property/event we are referring to, or * [a path to the property containing the reference, a path to the property/event we are referring to] @@ -48,8 +49,8 @@ import type { LinkDecl } from 'components/friends/sync/interface'; */ export function syncLinks(this: Sync, path?: LinkDecl, value?: unknown): void { let - linkPath, - storePath; + linkPath: CanUndef, + storePath: CanUndef; if (Object.isArray(path)) { storePath = path[0]; @@ -60,11 +61,18 @@ export function syncLinks(this: Sync, path?: LinkDecl, value?: unknown): void { } const + that = this, cache = this.syncLinkCache; - const sync = (linkName) => { - const - o = cache.get(linkName); + if (linkPath != null) { + sync(linkPath); + + } else { + cache.forEach((_, key) => sync(key)); + } + + function sync(linkName: string | object) { + const o = cache.get(linkName); if (o == null) { return; @@ -76,17 +84,8 @@ export function syncLinks(this: Sync, path?: LinkDecl, value?: unknown): void { } if (storePath == null || key === storePath) { - el.sync(value ?? this.field.get(linkName)); + el.sync(value ?? (Object.isString(linkName) ? that.field.get(linkName) : undefined)); } }); - }; - - if (linkPath != null) { - sync(linkPath); - - } else { - for (let o = cache.keys(), el = o.next(); !el.done; el = o.next()) { - sync(el.value); - } } } From 9574983c5f712389e93205bfef1bcd2740ff48f2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 21 Aug 2024 15:05:37 +0300 Subject: [PATCH 047/334] chore: optimization --- src/core/component/index.ts | 2 +- src/core/component/watch/const.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/component/index.ts b/src/core/component/index.ts index 949199081b..c80f865b52 100644 --- a/src/core/component/index.ts +++ b/src/core/component/index.ts @@ -20,7 +20,7 @@ export type { State } from 'core/component/state'; export { ComponentEngine as default } from 'core/component/engines'; export { runHook } from 'core/component/hook'; -export { bindRemoteWatchers, customWatcherRgxp } from 'core/component/watch'; +export { bindRemoteWatchers, isCustomWatcher, customWatcherRgxp } from 'core/component/watch'; export { callMethodFromComponent } from 'core/component/method'; export { normalizeClass, normalizeStyle } from 'core/component/render'; diff --git a/src/core/component/watch/const.ts b/src/core/component/watch/const.ts index 3367d4df12..e18cdae183 100644 --- a/src/core/component/watch/const.ts +++ b/src/core/component/watch/const.ts @@ -21,4 +21,5 @@ export const toComponentObject = Symbol('A link to the component object'); export const - customWatcherRgxp = /^([!?]?)([^!:?]*):(.*)/; + customWatcherRgxp = /^([!?]?)([^!:?]*):(.*)/, + isCustomWatcher = {test: (path: string): boolean => path.includes(':')}; From 56554160f2fd53822014209822490e08610aa533 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 21 Aug 2024 15:15:42 +0300 Subject: [PATCH 048/334] fix: fixed initializing of non-functional accessors --- src/core/component/decorators/component/index.ts | 4 +++- src/core/component/meta/fork.ts | 3 ++- src/core/component/meta/method.ts | 11 +++++++++-- src/core/component/reflect/constructor.ts | 6 +++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index e3ef7bfbbd..000ffbd35a 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -99,7 +99,9 @@ export function component(opts?: ComponentOptions): Function { components.set(componentOriginName, meta); } - initEmitter.once(`constructor.${componentOriginName}`, addMethodsToMeta.bind(null, meta, target)); + initEmitter.once(`constructor.${componentOriginName}`, () => { + addMethodsToMeta(components.get(componentOriginName)!, target); + }); }); return; diff --git a/src/core/component/meta/fork.ts b/src/core/component/meta/fork.ts index 4967085907..18ffec176e 100644 --- a/src/core/component/meta/fork.ts +++ b/src/core/component/meta/fork.ts @@ -15,9 +15,10 @@ import type { ComponentMeta } from 'core/component/interface'; export function forkMeta(base: ComponentMeta): ComponentMeta { const meta = Object.create(base); - meta.watchDependencies = new Map(meta.watchDependencies); meta.params = Object.create(base.params); + meta.watchDependencies = new Map(meta.watchDependencies); + meta.tiedFields = {...meta.tiedFields}; meta.hooks = {}; Object.entries(base.hooks).forEach(([name, handlers]) => { diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index a236c46d7a..849d5e0442 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -128,14 +128,21 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me }; } - store[name] = Object.assign(store[name] ?? {}, { + const acc = Object.assign(store[name] ?? {}, { src, get: desc.get ?? old?.get, set }); + store[name] = acc; + + // eslint-disable-next-line eqeqeq + if (acc.functional === undefined && meta.params.functional === null) { + acc.functional = false; + } + if (tiedWith != null) { - store[name].tiedWith = tiedWith; + acc.tiedWith = tiedWith; } } }); diff --git a/src/core/component/reflect/constructor.ts b/src/core/component/reflect/constructor.ts index a3dfafaa7c..0e71dd611e 100644 --- a/src/core/component/reflect/constructor.ts +++ b/src/core/component/reflect/constructor.ts @@ -90,6 +90,10 @@ export function getInfoFromConstructor( }; partialInfo.set(partial, info); + + // eslint-disable-next-line eqeqeq + } else if (declParams != null && declParams.functional !== null) { + declParams.functional = undefined; } componentParams.set(constructor, info.params); @@ -133,7 +137,7 @@ export function getInfoFromConstructor( } // Mix the "functional" parameter from the parent @component declaration - if (parentParams) { + if (parentParams != null) { let functional: typeof params.functional; if (Object.isDictionary(params.functional) && Object.isDictionary(parentParams.functional)) { From 9e705c50ab80a61ac0ac33a44319a72c151f045d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 21 Aug 2024 15:16:16 +0300 Subject: [PATCH 049/334] chore: stylish fixes --- src/core/component/decorators/interface/field.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/core/component/decorators/interface/field.ts b/src/core/component/decorators/interface/field.ts index 993dc7625d..93e869f91f 100644 --- a/src/core/component/decorators/interface/field.ts +++ b/src/core/component/decorators/interface/field.ts @@ -113,7 +113,7 @@ export interface DecoratorSystem< /** * Indicates that property should be initialized before all non-atom properties. - * This option is needed when you have a field that must be guaranteed to be initialized before other fields, + * This option is necessary when you have a field that must be guaranteed to be initialized before other fields, * and you don't want to use `after` everywhere. But you can still use `after` along with other atomic fields. * * @default `false` @@ -156,8 +156,7 @@ export interface DecoratorSystem< * * // Also, see core/object/watch * { - * // If set to false, - * // then a handler that is invoked on the watcher event does not take any arguments from the event + * // If set to false, the handler invoked on the watcher event does not take any arguments from the event * provideArgs: false, * * // How the event handler should be called: @@ -240,7 +239,7 @@ export interface DecoratorField< > extends DecoratorSystem { /** * If set to true, property changes will cause the template to be guaranteed to be re-rendered. - * Be aware that enabling this property may result in redundant redraws. + * Be aware that enabling this property may result in redundant redrawing. * * @default `false` */ From 7ab19fcbe0d6d8546a202b9b6b8d3012418bf88a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 21 Aug 2024 15:25:28 +0300 Subject: [PATCH 050/334] chore: optimization & refactoring --- src/components/super/i-block/event/index.ts | 7 +++- src/components/super/i-block/i-block.ts | 8 ++-- .../super/i-block/modules/lfc/index.ts | 3 +- src/components/super/i-block/props.ts | 4 +- src/components/super/i-block/state/index.ts | 2 +- src/components/super/i-data/data.ts | 24 ++++++++--- src/components/super/i-data/handlers.ts | 26 +++++++++--- src/components/super/i-data/i-data.ts | 2 + .../super/i-static-page/i-static-page.ts | 12 +++--- src/core/component/accessor/index.ts | 20 ++++++++- src/core/component/decorators/factory.ts | 4 +- .../component/decorators/interface/watcher.ts | 9 ++++ src/core/component/field/helpers.ts | 3 +- src/core/component/field/init.ts | 16 ++++--- src/core/component/hook/index.ts | 4 +- .../component/init/states/before-create.ts | 20 +++++---- src/core/component/init/states/mounted.ts | 3 +- src/core/component/interface/watch.ts | 18 ++++++++ src/core/component/render/wrappers.ts | 12 ++---- src/core/component/watch/bind.ts | 42 ++++++++++--------- 20 files changed, 165 insertions(+), 74 deletions(-) diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index b656f8b184..ae897a3221 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -572,7 +572,12 @@ export default abstract class iBlockEvent extends iBlockBase { * * @param enable */ - @watch({path: 'proxyCall', immediate: true}) + @watch({ + path: 'proxyCall', + immediate: true, + test: (ctx) => ctx.proxyCall != null + }) + protected initCallChildListener(enable: boolean): void { const label = {label: $$.initCallChildListener}; this.parentEmitter.off(label); diff --git a/src/components/super/i-block/i-block.ts b/src/components/super/i-block/i-block.ts index 0670a32ddf..f40e96d02f 100644 --- a/src/components/super/i-block/i-block.ts +++ b/src/components/super/i-block/i-block.ts @@ -11,9 +11,10 @@ * @packageDocumentation */ -import { component, hook, watch, UnsafeGetter } from 'core/component'; +import { component, UnsafeGetter } from 'core/component'; import type { Classes } from 'components/friends/provide'; +import { hook, watch } from 'components/super/i-block/decorators'; import type { ModVal, ModsDecl, ModsProp, ModsDict } from 'components/super/i-block/modules/mods'; import type { UnsafeIBlock } from 'components/super/i-block/interface'; @@ -91,9 +92,10 @@ export default abstract class iBlock extends iBlockProviders { * Handler: fixes the issue where the teleported component * and its DOM nodes were rendered before the teleport container was ready */ - @watch({ + @watch({ path: 'r.shouldMountTeleports', - flush: 'post' + flush: 'post', + test: (ctx) => HYDRATION && ctx.r.shouldMountTeleports === false }) @hook('before:mounted') diff --git a/src/components/super/i-block/modules/lfc/index.ts b/src/components/super/i-block/modules/lfc/index.ts index 0c7ff98c40..09cf673ad7 100644 --- a/src/components/super/i-block/modules/lfc/index.ts +++ b/src/components/super/i-block/modules/lfc/index.ts @@ -86,8 +86,7 @@ export default class Lfc extends Friend { this.ctx.beforeReadyListeners++; - const - res = this.ctx.waitComponentStatus('beforeReady', cb, opts); + const res = this.ctx.waitComponentStatus('beforeReady', cb, opts); if (Object.isPromise(res)) { return res.catch(stderr); diff --git a/src/components/super/i-block/props.ts b/src/components/super/i-block/props.ts index 533f1e1a73..5d6b0658cf 100644 --- a/src/components/super/i-block/props.ts +++ b/src/components/super/i-block/props.ts @@ -268,8 +268,8 @@ export default abstract class iBlockProps extends ComponentInterface { * }); * ``` */ - @prop(Boolean) - readonly proxyCall: boolean = false; + @prop({type: Boolean, required: false}) + readonly proxyCall?: boolean; /** * If set to true, the component event dispatching mode is enabled. diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 133993b618..d437bc0c5a 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -40,7 +40,7 @@ export default abstract class iBlockState extends iBlockMods { * A list of additional dependencies to load during the component's initialization * {@link iBlock.dependenciesProp} */ - @system((o) => o.sync.link((val: Iterable) => Array.concat([], Object.isIterable(val) ? [...val] : val))) + @system((o) => o.sync.link((val?: Iterable) => val == null ? [] : [...val])) dependencies!: Module[]; /** diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index b0c81a20d9..2a98f9ca09 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -38,12 +38,15 @@ import type { } from 'components/super/i-data/interface'; -const - $$ = symbolGenerator(); +const $$ = symbolGenerator(); interface iDataData extends Trait {} -@component({partial: 'iData'}) +@component({ + partial: 'iData', + functional: null +}) + @derive(iDataProvider) abstract class iDataData extends iBlock implements iDataProvider { /** @@ -104,7 +107,14 @@ abstract class iDataData extends iBlock implements iDataProvider { * A list of converters from the raw `db` to the component field * {@link iDataProvider.componentConverter} */ - @system((o) => o.sync.link('componentConverter', (val) => Array.concat([], Object.isIterable(val) ? [...val] : val))) + @system((o) => o.sync.link('componentConverter', (val) => { + if (val == null) { + return []; + } + + return Object.isIterable(val) ? [...val] : [val]; + })) + componentConverters!: ComponentConverter[]; /** @@ -255,7 +265,11 @@ abstract class iDataData extends iBlock implements iDataProvider { * This method is used to map `db` to bean properties. * If the method is used, it must return some value other than undefined. */ - @watch('componentConverter') + @watch({ + path: 'componentConverter', + test: (ctx) => ctx.componentConverter != null + }) + protected initRemoteData(): CanUndef { return undefined; } diff --git a/src/components/super/i-data/handlers.ts b/src/components/super/i-data/handlers.ts index 4ee2a608e7..10dfb43ae3 100644 --- a/src/components/super/i-data/handlers.ts +++ b/src/components/super/i-data/handlers.ts @@ -14,13 +14,19 @@ import type { ModelMethod } from 'core/data'; import DataProvider from 'components/friends/data-provider'; import { component, watch } from 'components/super/i-block/i-block'; +import type iData from 'components/super/i-data/i-data'; import iDataData from 'components/super/i-data/data'; + import type { RequestParams, RetryRequestFn } from 'components/super/i-data/interface'; const $$ = symbolGenerator(); -@component({partial: 'iData'}) +@component({ + partial: 'iData', + functional: null +}) + export default abstract class iDataHandlers extends iDataData { protected override initGlobalEvents(resetListener?: boolean): void { super.initGlobalEvents(resetListener != null ? resetListener : Boolean(this.dataProvider)); @@ -133,14 +139,22 @@ export default abstract class iDataHandlers extends iDataData { * Synchronization of `dataProvider` properties * @param [initLoad] - if false, there is no need to call `initLoad` */ - @watch([ - {path: 'dataProviderProp', provideArgs: false}, - {path: 'dataProviderOptions', provideArgs: false} + @watch([ + { + path: 'dataProviderProp', + provideArgs: false, + test: (ctx) => ctx.dataProviderProp != null + }, + + { + path: 'dataProviderOptions', + provideArgs: false, + test: (ctx) => ctx.dataProviderOptions != null + } ]) protected syncDataProviderWatcher(initLoad: boolean = true): void { - const - {dataProviderProp} = this; + const {dataProviderProp} = this; if (this.dataProvider != null) { this.async diff --git a/src/components/super/i-data/i-data.ts b/src/components/super/i-data/i-data.ts index 4ec1028fe1..173740d147 100644 --- a/src/components/super/i-data/i-data.ts +++ b/src/components/super/i-data/i-data.ts @@ -22,6 +22,7 @@ import type iBlock from 'components/super/i-block/i-block'; import { component, + computed, InitLoadCb, InitLoadOptions, @@ -59,6 +60,7 @@ const @component({functional: null}) export default abstract class iData extends iDataHandlers { + @computed({functional: true}) override get unsafe(): UnsafeGetter> { return Object.cast(this); } diff --git a/src/components/super/i-static-page/i-static-page.ts b/src/components/super/i-static-page/i-static-page.ts index 00f15cecf9..81ea486012 100644 --- a/src/components/super/i-static-page/i-static-page.ts +++ b/src/components/super/i-static-page/i-static-page.ts @@ -126,6 +126,12 @@ export default abstract class iStaticPage extends iPage { @system((o) => o.remoteState.route) initialRoute?: InitialRoute | this['CurrentPage']; + /** + * True if component teleports should be mounted + */ + @field() + shouldMountTeleports: boolean = false; + /** * Number of external root instances of the application. * This parameter allows you to detect memory leaks when using asyncRender. @@ -170,12 +176,6 @@ export default abstract class iStaticPage extends iPage { return this[$$.randomGenerator]; } - /** - * True if component teleports should be mounted - */ - @field() - protected shouldMountTeleports: boolean = false; - /** * The route information object store * {@link iStaticPage.route} diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index 0e299f5a46..d474ad2c19 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -69,7 +69,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { meta, // eslint-disable-next-line deprecation/deprecation - meta: {params: {deprecatedProps}} + meta: {params: {deprecatedProps}, tiedFields} } = component.unsafe; const isFunctional = meta.params.functional === true; @@ -81,9 +81,18 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { !SSR && isFunctional && accessor.functional === false; if (canSkip) { + const tiedWith = tiedFields[name]; + + if (tiedWith != null) { + delete tiedFields[tiedWith]; + delete tiedFields[name]; + } + return; } + delete tiedFields[name]; + Object.defineProperty(component, name, { configurable: true, enumerable: true, @@ -102,9 +111,18 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { !SSR && isFunctional && computed.functional === false; if (canSkip) { + const tiedWith = tiedFields[name]; + + if (tiedWith != null) { + delete tiedFields[tiedWith]; + delete tiedFields[name]; + } + return; } + delete tiedFields[name]; + // eslint-disable-next-line func-style const get = function get(this: typeof component): unknown { const {hook} = this; diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index 884d83fe77..6d4db3dd18 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -259,7 +259,9 @@ export function paramsFactory( } if (tiedFieldMap[metaKey] != null && isStore.test(key)) { - meta.tiedFields[key] = isStore.replace(key); + const tiedWith = isStore.replace(key); + meta.tiedFields[key] = tiedWith; + meta.tiedFields[tiedWith] = key; } function inheritFromParent() { diff --git a/src/core/component/decorators/interface/watcher.ts b/src/core/component/decorators/interface/watcher.ts index aad55b932a..c7a631b137 100644 --- a/src/core/component/decorators/interface/watcher.ts +++ b/src/core/component/decorators/interface/watcher.ts @@ -55,6 +55,15 @@ export interface DecoratorFieldWatcherObject< * ``` */ provideArgs?: boolean; + + /** + * A function to determine whether a watcher should be initialized or not. + * If the function returns false, the watcher will not be initialized. + * Useful for precise component optimizations. + * + * @param ctx + */ + test?(ctx: CTX): boolean; } export interface DecoratorWatchHandler { diff --git a/src/core/component/field/helpers.ts b/src/core/component/field/helpers.ts index d8ca4e133e..edcf59c9d9 100644 --- a/src/core/component/field/helpers.ts +++ b/src/core/component/field/helpers.ts @@ -34,8 +34,7 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar weight += after.size; after.forEach((name) => { - const - dep = scope[name]; + const dep = scope[name]; if (dep == null) { throw new ReferenceError(`The specified dependency ${dep} could not be found in the given scope`); diff --git a/src/core/component/field/init.ts b/src/core/component/field/init.ts index d0902c6edc..059255293a 100644 --- a/src/core/component/field/init.ts +++ b/src/core/component/field/init.ts @@ -49,8 +49,7 @@ export function initFields( unsafe.$activeField = name; - let - val: unknown; + let val: unknown; if (field.init != null) { val = field.init(component.unsafe, store); @@ -60,16 +59,23 @@ export function initFields( if (store[name] === undefined) { // To prevent linking to the same type of component for non-primitive values, // it's important to clone the default value from the component constructor. - val = field.default !== undefined ? field.default : Object.fastClone(instance[name]); + val = field.default !== undefined ? + field.default : + + (() => { + const v = instance[name]; + return Object.isPrimitive(v) ? v : Object.fastClone(v); + })(); + store[name] = val; } } else { store[name] = val; } - - unsafe.$activeField = undefined; }); + unsafe.$activeField = undefined; + return store; } diff --git a/src/core/component/hook/index.ts b/src/core/component/hook/index.ts index 9d6987a697..690cfe125f 100644 --- a/src/core/component/hook/index.ts +++ b/src/core/component/hook/index.ts @@ -44,13 +44,13 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn m = component.unsafe.meta, hooks: ComponentHook[] = []; - if (`before:${hook}` in m.hooks) { + if (hook === 'created' || hook === 'updated' || hook === 'mounted') { hooks.push(...m.hooks[`before:${hook}`]); } hooks.push(...m.hooks[hook]); - if (`after:${hook}` in m.hooks) { + if (hook === 'beforeDataCreate') { hooks.push(...m.hooks[`after:${hook}`]); } diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index cc8f31f392..e48d631381 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -31,6 +31,10 @@ import { destroyedState } from 'core/component/init/states/destroyed'; import type { ComponentInterface, ComponentMeta, ComponentElement, ComponentDestructorOptions } from 'core/component/interface'; import type { InitBeforeCreateStateOptions } from 'core/component/init/interface'; +const + $getRoot = Symbol('$getRoot'), + $getParent = Symbol('$getParent'); + /** * Initializes the "beforeCreate" state to the specified component instance * @@ -95,8 +99,6 @@ export function beforeCreateState( } }); - const $getRoot = Symbol('$getRoot'); - Object.defineProperty(unsafe, '$getRoot', { configurable: true, enumerable: false, @@ -133,8 +135,6 @@ export function beforeCreateState( } }); - const $getParent = Symbol('$getParent'); - Object.defineProperty(unsafe, '$getParent', { configurable: true, enumerable: false, @@ -342,12 +342,16 @@ export function beforeCreateState( return; } - const needToForceWatching = watchers[name] == null && ( - accessors[normalizedName] != null || - computedFields[normalizedName] != null + const + accessor = accessors[normalizedName], + computed = accessor == null ? computedFields[normalizedName] : null; + + const needForceWatch = watchers[name] == null && ( + accessor != null && accessor.dependencies?.length !== 0 || + computed != null && computed.cache !== 'forever' && computed.dependencies?.length !== 0 ); - if (needToForceWatching) { + if (needForceWatch) { watchers[name] = [ { deep: true, diff --git a/src/core/component/init/states/mounted.ts b/src/core/component/init/states/mounted.ts index 65ab733e1e..7886f97e62 100644 --- a/src/core/component/init/states/mounted.ts +++ b/src/core/component/init/states/mounted.ts @@ -17,8 +17,7 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function mountedState(component: ComponentInterface): void { - const - {$el} = component; + const {$el} = component; if ($el != null && $el.component !== component) { $el.component = component; diff --git a/src/core/component/interface/watch.ts b/src/core/component/interface/watch.ts index d47911a14a..a149b16cce 100644 --- a/src/core/component/interface/watch.ts +++ b/src/core/component/interface/watch.ts @@ -144,6 +144,15 @@ export interface WatchObject< * A handler, or the name of a component's method, that gets invoked upon watcher events */ handler: string | WatchHandler; + + /** + * A function to determine whether a watcher should be initialized or not. + * If the function returns false, the watcher will not be initialized. + * Useful for precise component optimizations. + * + * @param ctx + */ + test?(ctx: CTX): boolean; } export interface MethodWatcher< @@ -211,6 +220,15 @@ export interface MethodWatcher< * ``` */ wrapper?: WatchWrapper; + + /** + * A function to determine whether a watcher should be initialized or not. + * If the function returns false, the watcher will not be initialized. + * Useful for precise component optimizations. + * + * @param ctx + */ + test?(ctx: CTX): boolean; } export type WatchPath = diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 90d50de810..3188fef28b 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -265,8 +265,7 @@ export function wrapResolveComponent( */ export function wrapMergeProps(original: T): T { return Object.cast(function mergeProps(this: ComponentInterface, ...args: Parameters) { - const - props = original.apply(null, args); + const props = original.apply(null, args); if (SSR) { return resolveAttrs.call(this, {props}).props; @@ -355,8 +353,7 @@ export function wrapRenderList(original: T): T { return Object.cast(function renderSlot(this: ComponentInterface, ...args: Parameters) { - const - {r} = this.$renderEngine; + const {r} = this.$renderEngine; if (this.meta.params.functional === true) { try { @@ -424,8 +421,7 @@ export function wrapWithDirectives(_: T): T { this; dirs.forEach((decl) => { - const - [dir, value, arg, modifiers] = decl; + const [dir, value, arg, modifiers] = decl; const binding: DirectiveBinding = { dir: Object.isFunction(dir) ? {created: dir, mounted: dir} : dir, diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 3c4d950b30..8d1a85d7e2 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -10,7 +10,7 @@ import { wrapWithSuspending, EventId, EventEmitterLike, EventEmitterLikeP } from import { getPropertyInfo } from 'core/component/reflect'; import { beforeHooks } from 'core/component/const'; -import { customWatcherRgxp } from 'core/component/watch/const'; +import { isCustomWatcher, customWatcherRgxp } from 'core/component/watch/const'; import type { ComponentInterface } from 'core/component/interface'; import type { BindRemoteWatchersParams } from 'core/component/watch/interface'; @@ -72,15 +72,29 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // Custom watchers look like ':foo', 'bla:foo', '?bla:foo' // and are used to listen to custom events instead of property mutations. - const customWatcher = customWatcherRgxp.exec(watchPath); + const customWatcher = isCustomWatcher.test(watchPath) ? customWatcherRgxp.exec(watchPath) : null; - if (customWatcher) { + if (customWatcher != null) { const m = customWatcher[1]; watcherNeedCreated = m === ''; watcherNeedMounted = m === '?'; } - const attachWatcher = () => { + // Add a listener to a component's created hook if the component has not yet been created + if (watcherNeedCreated && isBeforeCreate) { + hooks['before:created'].push({fn: attachWatcher}); + return; + } + + // Add a listener to a component's mounted/activated hook if the component has not yet been mounted or activated + if (watcherNeedMounted && (isBeforeCreate || component.$el == null)) { + hooks[isDeactivated ? 'activated' : 'mounted'].unshift({fn: attachWatcher}); + return; + } + + attachWatcher(); + + function attachWatcher() { // If we have a custom watcher, we need to find a link to the event emitter. // For instance: // ':foo' -> watcherCtx == ctx; key = 'foo' @@ -99,7 +113,11 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR } // Iterates over all registered handlers for this watcher - watchers.forEach((watchInfo) => { + watchers!.forEach((watchInfo) => { + if (watchInfo.test?.(component) === false) { + return; + } + const rawHandler = watchInfo.handler; const asyncParams = { @@ -346,20 +364,6 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR unwatch = $watch.call(component, toWatch, watchInfo, handler); } }); - }; - - // Add a listener to a component's created hook if the component has not yet been created - if (watcherNeedCreated && isBeforeCreate) { - hooks['before:created'].push({fn: attachWatcher}); - return; - } - - // Add a listener to a component's mounted/activated hook if the component has not yet been mounted or activated - if (watcherNeedMounted && (isBeforeCreate || component.$el == null)) { - hooks[isDeactivated ? 'activated' : 'mounted'].unshift({fn: attachWatcher}); - return; } - - attachWatcher(); }); } From 99ba94fb343856629fc69c627a1d7c0a2e3a0404 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 21 Aug 2024 15:33:49 +0300 Subject: [PATCH 051/334] chore: optimization --- src/components/super/i-block/base/index.ts | 45 ++++++++++++---------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index ceceb179ce..a506d29492 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -60,6 +60,8 @@ const $$ = symbolGenerator(), i18nKeysets = new Map(); +const $propIds = Symbol('propIds'); + @component({partial: 'iBlock'}) export default abstract class iBlockBase extends iBlockFriends { /** @inheritDoc */ @@ -83,13 +85,13 @@ export default abstract class iBlockBase extends iBlockFriends { let id = o.componentIdProp; if (id != null) { - if (!($$.propIds in r)) { - r[$$.propIds] = Object.createDict(); + if (!($propIds in r)) { + r[$propIds] = Object.createDict(); } const propId = id, - propIds = r[$$.propIds]; + propIds = r[$propIds]; if (propIds[propId] != null) { id += `-${propIds[propId]++}`; @@ -112,28 +114,30 @@ export default abstract class iBlockBase extends iBlockFriends { * {@link iBlock.activatedProp} */ @system((o) => { - void o.lfc.execCbAtTheRightTime(() => { - if (o.isFunctional && !o.field.get('forceActivation')) { - return; - } + if (!o.isFunctional || o.forceActivation) { + void o.lfc.execCbAtTheRightTime(() => { + if (o.isActivated) { + o.activate(true); - if (o.field.get('isActivated')) { - o.activate(true); + } else { + o.deactivate(); + } + }); + } - } else { - o.deactivate(); - } - }); + if (!o.isFunctional) { + o.watch('activatedProp', (val: CanUndef) => { + val = val !== false; - return o.sync.link('activatedProp', (val: CanUndef) => { - val = val !== false; + if (o.hook !== 'beforeDataCreate') { + o[val ? 'activate' : 'deactivate'](); + } - if (o.hook !== 'beforeDataCreate') { - o[val ? 'activate' : 'deactivate'](); - } + o.isActivated = val; + }); + } - return val; - }); + return o.activatedProp; }) isActivated!: boolean; @@ -156,6 +160,7 @@ export default abstract class iBlockBase extends iBlockFriends { /** * A dictionary containing additional attributes for the component's root element */ + @computed({dependencies: []}) get rootAttrs(): Dictionary { return this.field.get('rootAttrsStore')!; } From f5057ec4280f2826734d7134dc4c954b2833142d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 21 Aug 2024 16:49:10 +0300 Subject: [PATCH 052/334] fix: fix after refactoring --- src/components/friends/sync/link.ts | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 1735a36276..d9528c92f1 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -375,16 +375,9 @@ export function link( linksCache[destPath] = {}; - const sync = (val?: unknown, oldVal?: unknown, src?: object) => { - const res = getter != null ? getter.call(this.component, val, oldVal) : val; - - if (src != null) { - src[destPath!] = res; - - } else { - this.field.set(destPath!, res); - } - + const sync = (val?: unknown, oldVal?: unknown) => { + const res = getter ? getter.call(this.component, val, oldVal) : val; + this.field.set(destPath!, res); return res; }; @@ -460,8 +453,7 @@ export function link( return sync(); } - const - needCollapse = resolvedOpts.collapse !== false; + const needCollapse = resolvedOpts.collapse !== false; if (mountedWatcher) { const obj = info?.ctx; @@ -473,14 +465,9 @@ export function link( return sync(Object.get(obj, normalizedPath)); } - const initSync = () => { - const src = - info.type === 'field' && (this.hook === 'beforeDataCreate' || ctx.isFunctionalWatchers) ? - ctx.$fields : - ctx; - - return sync(src[needCollapse ? info.originalTopPath : info.originalPath], undefined, src); - }; + const initSync = () => sync( + this.field.get(needCollapse ? info.originalTopPath : info.originalPath) + ); if (this.lfc.isBeforeCreate('beforeDataCreate')) { meta.hooks.beforeDataCreate.splice(this.lastSyncIndex++, 0, {fn: initSync}); From 8510d903a430fdc820b13b33f91298fff560b885 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 21 Aug 2024 17:01:18 +0300 Subject: [PATCH 053/334] fix: optimization --- src/components/friends/sync/link.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index d9528c92f1..335ba789d2 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -465,9 +465,14 @@ export function link( return sync(Object.get(obj, normalizedPath)); } - const initSync = () => sync( - this.field.get(needCollapse ? info.originalTopPath : info.originalPath) - ); + const initSync = () => { + const src = + info.type === 'field' && (this.hook === 'beforeDataCreate' || ctx.isFunctionalWatchers) ? + ctx.$fields : + ctx; + + return sync(src[needCollapse ? info.originalTopPath : info.originalPath]); + }; if (this.lfc.isBeforeCreate('beforeDataCreate')) { meta.hooks.beforeDataCreate.splice(this.lastSyncIndex++, 0, {fn: initSync}); From 6c1210e7198ad800c3ae2719ab91cb65734f2a7b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 22 Aug 2024 13:47:27 +0300 Subject: [PATCH 054/334] chore: optimization --- src/components/friends/field/delete.ts | 2 +- src/components/friends/field/get.ts | 2 +- src/components/friends/field/set.ts | 2 +- src/core/component/functional/context.ts | 5 ++- src/core/component/meta/fill.ts | 53 ++++++++++++++---------- src/core/component/prop/init.ts | 11 ++++- src/core/component/reflect/property.ts | 4 +- 7 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index 4325d452f3..2964d2cc09 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -90,7 +90,7 @@ export function deleteField( let isComponent = false; - if ((obj).instance instanceof iBlock) { + if ('componentName' in obj && 'unsafe' in obj) { ctx = (obj).unsafe; isComponent = true; } diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index e84f53f8c8..eac3afe609 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -83,7 +83,7 @@ export function getField( let isComponent = false; - if ((obj).instance instanceof iBlock) { + if ('componentName' in obj && 'unsafe' in obj) { ctx = (obj).unsafe; isComponent = true; } diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index 5fe360888b..261950f429 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -88,7 +88,7 @@ export function setField( let isComponent = false; - if ((obj).instance instanceof iBlock) { + if ('componentName' in obj && 'unsafe' in obj) { ctx = (obj).unsafe; isComponent = true; } diff --git a/src/core/component/functional/context.ts b/src/core/component/functional/context.ts index d6affd0309..75ff726559 100644 --- a/src/core/component/functional/context.ts +++ b/src/core/component/functional/context.ts @@ -105,7 +105,10 @@ export function createVirtualContext( componentName: meta.componentName, meta, - instance: Object.cast(meta.instance), + + get instance(): typeof meta['instance'] { + return meta.instance; + }, $props, $attrs, diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 57879b9187..cddf8fbdca 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -89,6 +89,10 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor } }); + requestIdleCallback(() => { + void meta.instance; + }); + const isRoot = params.root === true, isFunctional = params.functional === true; @@ -105,33 +109,36 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor } if (isFirstFill) { - let - getDefault: unknown, - skipDefault = true; - - if (defaultProps || prop.forceDefault) { - const defaultInstanceValue = meta.instance[propName]; - - skipDefault = false; - getDefault = defaultInstanceValue; - - // If the default value of a field is set via default values for a class property, - // it is necessary to clone this value for each new component instance - // to ensure that they do not share the same value - const needCloneDefValue = - defaultInstanceValue != null && typeof defaultInstanceValue === 'object' && - (!isTypeCanBeFunc(prop.type) || !Object.isFunction(defaultInstanceValue)); - - if (needCloneDefValue) { - getDefault = () => Object.fastClone(defaultInstanceValue); - (getDefault)[DEFAULT_WRAPPER] = true; - } - } + const skipDefault = !defaultProps && !prop.forceDefault; let defaultValue: unknown; if (!skipDefault) { - defaultValue = prop.default !== undefined ? prop.default : getDefault; + if (prop.default !== undefined) { + defaultValue = prop.default; + + } else { + const defaultInstanceValue = meta.instance[propName]; + + let getDefault = defaultInstanceValue; + + // If the default value of a field is set via default values for a class property, + // it is necessary to clone this value for each new component instance + // to ensure that they do not share the same value + const needCloneDefValue = + defaultInstanceValue != null && typeof defaultInstanceValue === 'object' && + (!isTypeCanBeFunc(prop.type) || !Object.isFunction(defaultInstanceValue)); + + if (needCloneDefValue) { + getDefault = () => Object.isPrimitive(defaultInstanceValue) ? + defaultInstanceValue : + Object.fastClone(defaultInstanceValue); + + (getDefault)[DEFAULT_WRAPPER] = true; + } + + defaultValue = getDefault; + } } if (!isRoot || defaultValue !== undefined) { diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index 8e3d86a1fd..c12932392a 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -68,7 +68,16 @@ export function initProps( } if (propValue === undefined) { - propValue = prop.default !== undefined ? prop.default : Object.fastClone(meta.instance[name]); + if (prop.default !== undefined) { + propValue = prop.default; + + } else { + propValue = meta.instance[name]; + + if (!Object.isPrimitive(propValue)) { + propValue = Object.fastClone(propValue); + } + } } let needSaveToStore = opts.saveToStore; diff --git a/src/core/component/reflect/property.ts b/src/core/component/reflect/property.ts index 4b479ab9dc..0ea4841092 100644 --- a/src/core/component/reflect/property.ts +++ b/src/core/component/reflect/property.ts @@ -7,7 +7,7 @@ */ import { deprecate } from 'core/functools/deprecation'; -import { ComponentInterface } from 'core/component/interface'; +import type { ComponentInterface } from 'core/component/interface'; import { isStore, isPrivateField } from 'core/component/reflect/const'; import type { PropertyInfo, AccessorType } from 'core/component/reflect/interface'; @@ -71,7 +71,7 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr obj = obj[chunk]; - if (obj?.instance instanceof ComponentInterface) { + if (obj != null && typeof obj === 'object' && 'componentName' in obj) { component = obj; rootI = i === chunks.length - 1 ? i : i + 1; } From 799f857af5e66e3829f976e0206c187d4271eae6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 22 Aug 2024 14:56:38 +0300 Subject: [PATCH 055/334] feat: added a new method `getFieldsStore` --- src/components/friends/field/class.ts | 8 +++++++- src/components/friends/field/get.ts | 10 +++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/friends/field/class.ts b/src/components/friends/field/class.ts index 1db2dd734d..d998a2aa94 100644 --- a/src/components/friends/field/class.ts +++ b/src/components/friends/field/class.ts @@ -26,7 +26,13 @@ interface Field { @fakeMethods('delete') class Field extends Friend { - + /** + * Returns a reference to the storage object for the component fields + */ + getFieldsStore(): T { + const {ctx} = this; + return Object.cast(ctx.isFunctionalWatchers || this.lfc.isBeforeCreate() ? ctx.$fields : ctx); + } } Field.addToPrototype({ diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index eac3afe609..35a1421832 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type Friend from 'components/friends/friend'; +import type Field from 'components/friends/field'; import iBlock, { getPropertyInfo } from 'components/super/i-block/i-block'; import type { ValueGetter } from 'components/friends/field/interface'; @@ -39,7 +39,7 @@ import type { ValueGetter } from 'components/friends/field/interface'; * } * ``` */ -export function getField(this: Friend, path: string, getter: ValueGetter): CanUndef; +export function getField(this: Field, path: string, getter: ValueGetter): CanUndef; /** * Returns a property from the passed object at the specified path @@ -58,14 +58,14 @@ export function getField(this: Friend, path: string, getter: ValueG * ``` */ export function getField( - this: Friend, + this: Field, path: string, obj?: Nullable, getter?: ValueGetter ): CanUndef; export function getField( - this: Friend, + this: Field, path: string, obj: Nullable = this.ctx, getter?: ValueGetter @@ -113,7 +113,7 @@ export function getField( break; case 'field': - res = ctx.isFunctionalWatchers || ctx.lfc.isBeforeCreate() ? ctx.$fields : ctx; + res = this.getFieldsStore(); break; default: From bd59d5387765c34ab1ec57c16c9fd45d76af7d31 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 22 Aug 2024 17:09:50 +0300 Subject: [PATCH 056/334] chore: using `field.getFieldsStore` --- src/components/friends/sync/link.ts | 33 ++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 335ba789d2..8eb13093b3 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -376,9 +376,25 @@ export function link( linksCache[destPath] = {}; const sync = (val?: unknown, oldVal?: unknown) => { - const res = getter ? getter.call(this.component, val, oldVal) : val; - this.field.set(destPath!, res); - return res; + const resolveVal = getter ? getter.call(this.component, val, oldVal) : val; + + if (destPath == null) { + return resolveVal; + } + + const info = getPropertyInfo(destPath, this.component); + + if (destPath.includes('.') || info.type === 'mounted') { + this.field.set(destPath, resolveVal); + + } else if (info.type === 'field') { + this.field.getFieldsStore()[destPath] = resolveVal; + + } else { + info.ctx[destPath] = resolveVal; + } + + return resolveVal; }; if (getter != null && (getter.length > 1 || getter['originalLength'] > 1)) { @@ -466,12 +482,13 @@ export function link( } const initSync = () => { - const src = - info.type === 'field' && (this.hook === 'beforeDataCreate' || ctx.isFunctionalWatchers) ? - ctx.$fields : - ctx; + const path: string = needCollapse ? info.originalTopPath : info.originalPath; + + if (path.includes('.')) { + return sync(this.field.get(path)); + } - return sync(src[needCollapse ? info.originalTopPath : info.originalPath]); + return sync(info.type === 'field' ? this.field.getFieldsStore()[path] : info.ctx[path]); }; if (this.lfc.isBeforeCreate('beforeDataCreate')) { From 576291ae1bb21549d820345eb0fc41209babeb54 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 22 Aug 2024 17:18:26 +0300 Subject: [PATCH 057/334] chore: stylish fixes --- src/components/super/i-block/test/unit/props.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/test/unit/props.ts b/src/components/super/i-block/test/unit/props.ts index 75e818dd64..c6515b4804 100644 --- a/src/components/super/i-block/test/unit/props.ts +++ b/src/components/super/i-block/test/unit/props.ts @@ -183,7 +183,7 @@ test.describe(' props', () => { }); test.describe('`watchProp` should call `setStage` method', () => { - test('when the parent\'s `stage` property changes', async ({page}) => { + test("when the parent's `stage` property changes", async ({page}) => { const target = await renderDummy(page, { watchProp: { setStage: 'stage' From 7be9fa8defcf1b4327bf7a18176e833b2447e2ba Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 22 Aug 2024 17:51:34 +0300 Subject: [PATCH 058/334] fix: fixes after refactoring --- src/components/friends/field/class.ts | 9 +++++---- src/components/friends/field/get.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/friends/field/class.ts b/src/components/friends/field/class.ts index d998a2aa94..8ca48b8c54 100644 --- a/src/components/friends/field/class.ts +++ b/src/components/friends/field/class.ts @@ -27,11 +27,12 @@ interface Field { @fakeMethods('delete') class Field extends Friend { /** - * Returns a reference to the storage object for the component fields + * Returns a reference to the storage object for the fields of the passed component + * @param [component] */ - getFieldsStore(): T { - const {ctx} = this; - return Object.cast(ctx.isFunctionalWatchers || this.lfc.isBeforeCreate() ? ctx.$fields : ctx); + getFieldsStore(component: T = Object.cast(this.component)): T { + const unsafe = Object.cast(component); + return Object.cast(unsafe.isFunctionalWatchers || this.lfc.isBeforeCreate() ? unsafe.$fields : unsafe); } } diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index 35a1421832..7360162fae 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -113,7 +113,7 @@ export function getField( break; case 'field': - res = this.getFieldsStore(); + res = this.getFieldsStore(ctx); break; default: From 28178e59225410063f4c2a4f70ec01372b72d019 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 22 Aug 2024 18:13:45 +0300 Subject: [PATCH 059/334] fix: fixes after refactoring --- src/components/super/i-block/i-block.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/super/i-block/i-block.ts b/src/components/super/i-block/i-block.ts index f40e96d02f..46a785be75 100644 --- a/src/components/super/i-block/i-block.ts +++ b/src/components/super/i-block/i-block.ts @@ -94,25 +94,27 @@ export default abstract class iBlock extends iBlockProviders { */ @watch({ path: 'r.shouldMountTeleports', - flush: 'post', - test: (ctx) => HYDRATION && ctx.r.shouldMountTeleports === false + flush: 'post' }) @hook('before:mounted') protected onMountTeleports(): void { - const getNode = () => this.$refs[this.$resolveRef('$el')] ?? this.$el; - const { $el: originalNode, $async: $a } = this; - const - node = getNode(), - mountedAttrs = new Set(), - mountedAttrsGroup = {group: 'mountedAttrs'}; + if (originalNode == null) { + return; + } + + const getNode = () => this.$refs[this.$resolveRef('$el')] ?? this.$el; + + const node = getNode(); - if (originalNode != null && node != null && originalNode !== node) { + let attrsStore: CanNull> = null; + + if (node != null && originalNode !== node) { // Fix the DOM element link to the component originalNode.component = this; @@ -129,12 +131,16 @@ export default abstract class iBlock extends iBlockProviders { } function mountAttrs(attrs: Dictionary) { + const mountedAttrsGroup = {group: 'mountedAttrs'}; $a.terminateWorker(mountedAttrsGroup); if (node == null || originalNode == null) { return; } + attrsStore ??= new Set(); + const mountedAttrs = attrsStore; + Object.entries(attrs).forEach(([name, attr]) => { if (attr == null) { return; From 04810c5933730a4751e80d0ed58ffce64c8d8d64 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 22 Aug 2024 18:23:16 +0300 Subject: [PATCH 060/334] chore: optimization --- src/components/super/i-block/base/index.ts | 7 ++++++- src/components/super/i-block/state/index.ts | 17 +++++++++-------- src/components/super/i-data/data.ts | 7 +++---- src/components/super/i-data/i-data.ts | 2 +- .../super/i-static-page/i-static-page.ts | 6 +++--- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index a506d29492..9cdbf5a0d1 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -130,7 +130,12 @@ export default abstract class iBlockBase extends iBlockFriends { val = val !== false; if (o.hook !== 'beforeDataCreate') { - o[val ? 'activate' : 'deactivate'](); + if (val) { + o.activate(); + + } else { + o.deactivate(); + } } o.isActivated = val; diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index d437bc0c5a..9e185d2d4e 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -163,7 +163,8 @@ export default abstract class iBlockState extends iBlockMods { */ @computed() get componentStatus(): ComponentStatus { - return this.shadowComponentStatusStore ?? this.field.get('componentStatusStore') ?? 'unloaded'; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return this.shadowComponentStatusStore ?? this.field.getFieldsStore().componentStatusStore ?? 'unloaded'; } /** @@ -194,8 +195,7 @@ export default abstract class iBlockState extends iBlockMods { this.shadowComponentStatusStore = value; } else { - this.shadowComponentStatusStore = undefined; - this.field.set('componentStatusStore', value); + this.field.getFieldsStore().componentStatusStore = value; if (this.isReady && this.dependencies.length > 0) { void this.forceUpdate(); @@ -244,7 +244,7 @@ export default abstract class iBlockState extends iBlockMods { */ @computed() get stage(): CanUndef { - return this.field.get('stageStore'); + return this.field.getFieldsStore().stageStore; } /** @@ -264,7 +264,7 @@ export default abstract class iBlockState extends iBlockMods { } this.async.clearAll({group: this.stageGroup}); - this.field.set('stageStore', value); + this.field.getFieldsStore().stageStore = value; if (value != null) { this.emit(`stage:${value}`, value, oldValue); @@ -285,14 +285,15 @@ export default abstract class iBlockState extends iBlockMods { * A link to the application router */ get router(): CanUndef { - return this.field.get('routerStore', this.r); + // @ts-ignore (unsafe) + return this.r.routerStore; } /** * A link to the current route object */ get route(): CanUndef { - return this.field.get('route', this.r); + return this.r.route; } /** @@ -325,7 +326,7 @@ export default abstract class iBlockState extends iBlockMods { functionalWatching: false, init: (o) => o.sync.link>((val) => { o.stage = val; - return o.field.get('stageStore'); + return o.field.getFieldsStore().stageStore; }) }) diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index 2a98f9ca09..4b7e5c996f 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -155,7 +155,7 @@ abstract class iDataData extends iBlock implements iDataProvider { * The raw component data from the data provider */ get db(): CanUndef { - return this.field.get('dbStore'); + return this.field.getFieldsStore().dbStore; } /** @@ -172,14 +172,13 @@ abstract class iDataData extends iBlock implements iDataProvider { return; } - const - {async: $a} = this; + const {async: $a} = this; $a.terminateWorker({ label: $$.db }); - this.field.set('dbStore', value); + this.field.getFieldsStore().dbStore = value; if (this.initRemoteData() !== undefined) { this.watch('dbStore', this.initRemoteData.bind(this), { diff --git a/src/components/super/i-data/i-data.ts b/src/components/super/i-data/i-data.ts index 173740d147..0b23883f83 100644 --- a/src/components/super/i-data/i-data.ts +++ b/src/components/super/i-data/i-data.ts @@ -110,7 +110,7 @@ export default abstract class iData extends iDataHandlers { void this.db; }; - const hydrationMode = this.canUseHydratedData && Boolean(this.field.get('ssrRendering')); + const hydrationMode = this.canUseHydratedData && Boolean(this.field.getFieldsStore().ssrRendering); if (hydrationMode) { const diff --git a/src/components/super/i-static-page/i-static-page.ts b/src/components/super/i-static-page/i-static-page.ts index 81ea486012..c94652c447 100644 --- a/src/components/super/i-static-page/i-static-page.ts +++ b/src/components/super/i-static-page/i-static-page.ts @@ -144,7 +144,7 @@ export default abstract class iStaticPage extends iPage { */ @computed({dependencies: ['route.meta.name']}) get activePage(): CanUndef { - return this.field.get('route.meta.name'); + return this.route?.meta.name; } /** @@ -157,7 +157,7 @@ export default abstract class iStaticPage extends iPage { @computed() override get route(): CanUndef { - return this.field.get('routeStore'); + return this.field.getFieldsStore().routeStore; } /** @@ -167,7 +167,7 @@ export default abstract class iStaticPage extends iPage { * @emits `setRoute(value: CanUndef)` */ override set route(value: CanUndef) { - this.field.set('routeStore', value); + this.field.getFieldsStore().routeStore = value; this.emit('setRoute', value); } From ec7a8227303b6434031f397139fba6607c3643df Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 22 Aug 2024 18:29:40 +0300 Subject: [PATCH 061/334] chore: fixed TS --- src/components/friends/field/class.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/friends/field/class.ts b/src/components/friends/field/class.ts index 8ca48b8c54..cebc689cd5 100644 --- a/src/components/friends/field/class.ts +++ b/src/components/friends/field/class.ts @@ -30,7 +30,7 @@ class Field extends Friend { * Returns a reference to the storage object for the fields of the passed component * @param [component] */ - getFieldsStore(component: T = Object.cast(this.component)): T { + getFieldsStore(component: T = Object.cast(this.component)): Extract { const unsafe = Object.cast(component); return Object.cast(unsafe.isFunctionalWatchers || this.lfc.isBeforeCreate() ? unsafe.$fields : unsafe); } From 90a967f77e3da1f8371e50ac6d249ef800740063 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 00:15:24 +0300 Subject: [PATCH 062/334] chore: optimization --- src/core/component/decorators/factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index 6d4db3dd18..e5d26c179a 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -170,7 +170,7 @@ export function paramsFactory( }); } - if (p.dependencies != null) { + if (p.dependencies != null && p.dependencies.length > 0) { meta.watchDependencies.set(key, p.dependencies); } } From ce5de64903ed0c79ff156dcd1a004f55b7b74bd8 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 02:08:54 +0300 Subject: [PATCH 063/334] chore: optimization --- src/components/super/i-block/friends.ts | 57 ++++++++++++++++--------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/components/super/i-block/friends.ts b/src/components/super/i-block/friends.ts index 342fd99c29..30e7778739 100644 --- a/src/components/super/i-block/friends.ts +++ b/src/components/super/i-block/friends.ts @@ -6,29 +6,32 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import * as browser from 'core/browser'; -import * as helpers from 'core/helpers'; -import * as presets from 'components/presets'; +/* eslint-disable @typescript-eslint/no-var-requires */ -import Daemons, { DaemonsDict } from 'components/friends/daemons'; -import Analytics from 'components/friends/analytics'; +import type * as browser from 'core/browser'; +import type * as helpers from 'core/helpers'; +import type * as presets from 'components/presets'; -import DOM from 'components/friends/dom'; -import VDOM from 'components/friends/vdom'; -import Opt from 'components/super/i-block/modules/opt'; +import type Daemons from 'components/friends/daemons'; +import type { DaemonsDict } from 'components/friends/daemons'; -import AsyncRender from 'components/friends/async-render'; -import ModuleLoader from 'components/friends/module-loader'; -import Sync from 'components/friends/sync'; +import type Analytics from 'components/friends/analytics'; +import type DOM from 'components/friends/dom'; +import type VDOM from 'components/friends/vdom'; +import type Opt from 'components/super/i-block/modules/opt'; -import Field from 'components/friends/field'; -import Provide from 'components/friends/provide'; -import InfoRender from 'components/friends/info-render'; +import type AsyncRender from 'components/friends/async-render'; +import type ModuleLoader from 'components/friends/module-loader'; +import type Sync from 'components/friends/sync'; + +import type Field from 'components/friends/field'; +import type Provide from 'components/friends/provide'; +import type InfoRender from 'components/friends/info-render'; import type Block from 'components/friends/block'; -import Lfc from 'components/super/i-block/modules/lfc'; -import State from 'components/friends/state'; -import Storage from 'components/friends/storage'; +import type Lfc from 'components/super/i-block/modules/lfc'; +import type State from 'components/friends/state'; +import type Storage from 'components/friends/storage'; import { component } from 'core/component'; import { system, hook, computed } from 'components/super/i-block/decorators'; @@ -42,6 +45,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get provide(): Provide { + const Provide = require('components/friends/provide').default; return new Provide(Object.cast(this)); } @@ -55,6 +59,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get field(): Field { + const Field = require('components/friends/field').default; return new Field(Object.cast(this)); } @@ -75,6 +80,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get sync(): Sync { + const Sync = require('components/friends/sync').default; return new Sync(Object.cast(this)); } @@ -89,6 +95,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get asyncRender(): AsyncRender { + const AsyncRender = require('components/friends/async-render').default; return new AsyncRender(Object.cast(this)); } @@ -97,6 +104,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get vdom(): VDOM { + const VDOM = require('components/friends/vdom').default; return new VDOM(Object.cast(this)); } @@ -105,6 +113,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get infoRender(): InfoRender { + const InfoRender = require('components/friends/info-render').default; return new InfoRender(Object.cast(this)); } @@ -113,6 +122,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get analytics(): Analytics { + const Analytics = require('components/friends/analytics').default; return new Analytics(Object.cast(this)); } @@ -121,6 +131,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get lfc(): Lfc { + const Lfc = require('components/super/i-block/modules/lfc').default; return new Lfc(Object.cast(this)); } @@ -139,6 +150,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get dom(): DOM { + const DOM = require('components/friends/dom').default; return new DOM(Object.cast(this)); } @@ -147,6 +159,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get storage(): Storage { + const Storage = require('components/friends/storage').default; return new Storage(Object.cast(this)); } @@ -155,6 +168,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get state(): State { + const State = require('components/friends/state').default; return new State(Object.cast(this)); } @@ -163,6 +177,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get moduleLoader(): ModuleLoader { + const ModuleLoader = require('components/friends/module-loader').default; return new ModuleLoader(Object.cast(this)); } @@ -171,6 +186,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get daemons(): Daemons { + const Daemons = require('components/friends/daemons').default; return new Daemons(Object.cast(this)); } @@ -185,6 +201,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get opt(): Opt { + const Opt = require('components/super/i-block/modules/opt').default; return new Opt(Object.cast(this)); } @@ -193,7 +210,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get browser(): typeof browser { - return browser; + return require('core/browser'); } /** @@ -201,7 +218,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get presets(): typeof presets { - return presets; + return require('components/presets'); } /** @@ -210,7 +227,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get h(): typeof helpers { - return helpers; + return require('core/helpers'); } /** From 6f524f3566b3b5a1df0eadf4cbd51af7ce4b91f2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 13:39:42 +0300 Subject: [PATCH 064/334] chore: optimization --- src/components/friends/field/class.ts | 10 +++++++--- src/components/friends/sync/link.ts | 9 +++++---- src/components/friends/sync/mod.ts | 18 +++++++++++++++--- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/components/friends/field/class.ts b/src/components/friends/field/class.ts index cebc689cd5..abbfd71523 100644 --- a/src/components/friends/field/class.ts +++ b/src/components/friends/field/class.ts @@ -6,6 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import type { ComponentInterface } from 'core/component'; import Friend, { fakeMethods } from 'components/friends/friend'; import { getField } from 'components/friends/field/get'; @@ -30,9 +31,12 @@ class Field extends Friend { * Returns a reference to the storage object for the fields of the passed component * @param [component] */ - getFieldsStore(component: T = Object.cast(this.component)): Extract { - const unsafe = Object.cast(component); - return Object.cast(unsafe.isFunctionalWatchers || this.lfc.isBeforeCreate() ? unsafe.$fields : unsafe); + getFieldsStore(component?: T): T; + getFieldsStore(component?: T): T; + getFieldsStore(component?: T): T; + getFieldsStore(component?: T): T; + getFieldsStore(component: typeof this.ctx = this.ctx): object { + return Object.cast(component.isFunctionalWatchers || this.lfc.isBeforeCreate() ? component.$fields : component); } } diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 8eb13093b3..c5ad9f1e2a 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -384,11 +384,11 @@ export function link( const info = getPropertyInfo(destPath, this.component); - if (destPath.includes('.') || info.type === 'mounted') { + if (info.path.includes('.') || info.type === 'mounted') { this.field.set(destPath, resolveVal); } else if (info.type === 'field') { - this.field.getFieldsStore()[destPath] = resolveVal; + this.field.getFieldsStore(info.ctx)[destPath] = resolveVal; } else { info.ctx[destPath] = resolveVal; @@ -484,11 +484,12 @@ export function link( const initSync = () => { const path: string = needCollapse ? info.originalTopPath : info.originalPath; - if (path.includes('.')) { + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (info.path.includes('.')) { return sync(this.field.get(path)); } - return sync(info.type === 'field' ? this.field.getFieldsStore()[path] : info.ctx[path]); + return sync(info.type === 'field' ? this.field.getFieldsStore(info.ctx)[path] : info.ctx[path]); }; if (this.lfc.isBeforeCreate('beforeDataCreate')) { diff --git a/src/components/friends/sync/mod.ts b/src/components/friends/sync/mod.ts index d9c2795a74..00cba8bed7 100644 --- a/src/components/friends/sync/mod.ts +++ b/src/components/friends/sync/mod.ts @@ -6,6 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import { getPropertyInfo } from 'core/component'; import { statuses } from 'components/super/i-block/const'; import type Sync from 'components/friends/sync/class'; @@ -95,10 +96,12 @@ export function mod( opts = Object.cast(optsOrConverter); } - const that = this; - const {ctx} = this; + const + that = this, + info = getPropertyInfo(path, this.component); + if (this.lfc.isBeforeCreate()) { this.syncModCache[modName] = sync; @@ -118,7 +121,16 @@ export function mod( } function sync() { - const v = (converter).call(that.component, that.field.get(path)); + let v: unknown; + + if (info.path.includes('.')) { + v = that.field.get(path); + + } else { + v = info.type === 'field' ? that.field.getFieldsStore(info.ctx)[path] : info.ctx[path]; + } + + v = (converter).call(that.component, v); if (v !== undefined) { ctx.mods[modName] = String(v); From cc8255508d70acc1f4fed8be3f9465e14ecb9c14 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 13:41:14 +0300 Subject: [PATCH 065/334] chore: optimization --- src/components/super/i-block/base/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 9cdbf5a0d1..021e73fafc 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -167,7 +167,7 @@ export default abstract class iBlockBase extends iBlockFriends { */ @computed({dependencies: []}) get rootAttrs(): Dictionary { - return this.field.get('rootAttrsStore')!; + return this.field.getFieldsStore().rootAttrsStore; } /** From 1761d2db9b7700164041ed801b64d0b6666a2de3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 13:56:13 +0300 Subject: [PATCH 066/334] chore: optimization --- src/components/super/i-block/mods/index.ts | 1 - src/components/super/i-block/state/index.ts | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/super/i-block/mods/index.ts b/src/components/super/i-block/mods/index.ts index e464c0662d..1857d80160 100644 --- a/src/components/super/i-block/mods/index.ts +++ b/src/components/super/i-block/mods/index.ts @@ -26,7 +26,6 @@ export default abstract class iBlockMods extends iBlockEvent { @system({merge: mergeMods, init: initMods}) override readonly mods!: ModsDict; - @computed({dependencies: ['mods']}) override get sharedMods(): CanNull { const m = this.mods; diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 9e185d2d4e..1d170db74c 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -161,7 +161,7 @@ export default abstract class iBlockState extends iBlockMods { * 6. `destroyed` - the component has been destroyed: * this status might coincide with certain component hooks such as `beforeDestroy` or `destroyed`. */ - @computed() + @computed({cache: false, dependencies: []}) get componentStatus(): ComponentStatus { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition return this.shadowComponentStatusStore ?? this.field.getFieldsStore().componentStatusStore ?? 'unloaded'; @@ -242,7 +242,7 @@ export default abstract class iBlockState extends iBlockMods { * * {@link iBlock.stageProp} */ - @computed() + @computed({cache: false, dependencies: []}) get stage(): CanUndef { return this.field.getFieldsStore().stageStore; } @@ -284,6 +284,7 @@ export default abstract class iBlockState extends iBlockMods { /** * A link to the application router */ + @computed({cache: false, dependencies: []}) get router(): CanUndef { // @ts-ignore (unsafe) return this.r.routerStore; From 209d9972462f2fb19c820f27f181ce9bb25ce209 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 15:23:13 +0300 Subject: [PATCH 067/334] chore: optimization --- .../loaders/symbol-generator-loader.js | 11 +- components-lock.json | 2 +- package.json | 2 +- yarn.lock | 506 +++++++----------- 4 files changed, 187 insertions(+), 334 deletions(-) diff --git a/build/webpack/loaders/symbol-generator-loader.js b/build/webpack/loaders/symbol-generator-loader.js index 25f2c71514..0ecfd70551 100644 --- a/build/webpack/loaders/symbol-generator-loader.js +++ b/build/webpack/loaders/symbol-generator-loader.js @@ -8,9 +8,7 @@ 'use strict'; -const - config = require('@config/config'), - $C = require('collection.js'); +const $C = require('collection.js'); const fs = require('node:fs'), @@ -37,10 +35,6 @@ const * ``` */ module.exports = function symbolGeneratorLoader(str) { - if (!/ES[35]$/.test(config.es())) { - return str; - } - if ( !$C(this.query.modules) .some((src) => isPathInside(fs.realpathSync(this.context), fs.realpathSync(src))) @@ -48,8 +42,7 @@ module.exports = function symbolGeneratorLoader(str) { return str; } - const - names = new Set(); + const names = new Set(); let res; diff --git a/components-lock.json b/components-lock.json index c914533d2d..5fd6463359 100644 --- a/components-lock.json +++ b/components-lock.json @@ -2115,7 +2115,7 @@ }, "type": "global", "mixin": false, - "logic": null, + "logic": "src/components/global/g-hint/g-hint.ts", "styles": [ "src/components/global/g-hint/g-hint.styl" ], diff --git a/package.json b/package.json index e163ed768a..1c6b9d4435 100644 --- a/package.json +++ b/package.json @@ -165,7 +165,7 @@ "@types/semver": "7.3.10", "@types/webpack": "5.28.0", "@v4fire/cli": "1.6.0", - "@v4fire/core": "github:V4Fire/Core#v4", + "@v4fire/core": "github:V4Fire/Core#speedup", "@v4fire/linters": "git+https://github.com/v4fire/linters#rework_rules", "@v4fire/storybook": "0.8.0", "@v4fire/storybook-framework-webpack5": "0.8.0", diff --git a/yarn.lock b/yarn.lock index 670a89fd53..c6e9f02ad1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3094,13 +3094,13 @@ __metadata: languageName: node linkType: hard -"@statoscope/config@npm:5.22.0": - version: 5.22.0 - resolution: "@statoscope/config@npm:5.22.0" +"@statoscope/config@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/config@npm:5.28.1" dependencies: - "@statoscope/types": "npm:5.22.0" + "@statoscope/types": "npm:5.28.1" chalk: "npm:^4.1.2" - checksum: 831bcc6ba915663bbffe0d86a517dc66b6297549f165cbbcba5eee647740d3d623c0168e2e124a0a52b3cf37d8ffb50d74275a86a286ed15656b48cbe8cc72c7 + checksum: bfeb5bcf8e05242e5911920f93f7c3697e525ef894ba7a6895981b7969dd7986f66d8c3f6ace227ff2628d6b608bbc5dd613b29a2ec3d9029e4af45f47b484c4 languageName: node linkType: hard @@ -3111,6 +3111,13 @@ __metadata: languageName: node linkType: hard +"@statoscope/extensions@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/extensions@npm:5.28.1" + checksum: 995a5537457cd41441f0388d8c8c6ab99b85f8cd21ee9e23a372e4adeb327b541f59903cad542c95dd2c1fb9a4c61de344bc16998eea7f7fa17af7e6429204ec + languageName: node + linkType: hard + "@statoscope/helpers@npm:5.25.0": version: 5.25.0 resolution: "@statoscope/helpers@npm:5.25.0" @@ -3124,6 +3131,19 @@ __metadata: languageName: node linkType: hard +"@statoscope/helpers@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/helpers@npm:5.28.1" + dependencies: + "@types/archy": "npm:^0.0.32" + "@types/semver": "npm:^7.5.1" + archy: "npm:~1.0.0" + jora: "npm:1.0.0-beta.8" + semver: "npm:^7.5.4" + checksum: 51c9efe3129bc76a5da93e6c96fa4be90559dbe3504b693645bb00bbe7883f6e6c6319f9fda31b75074ccbe6e989d5820bfd3f801403f9a352102c019d0c3684 + languageName: node + linkType: hard + "@statoscope/report-writer@npm:5.25.1": version: 5.25.1 resolution: "@statoscope/report-writer@npm:5.25.1" @@ -3145,6 +3165,18 @@ __metadata: languageName: node linkType: hard +"@statoscope/stats-extension-compressed@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/stats-extension-compressed@npm:5.28.1" + dependencies: + "@statoscope/extensions": "npm:5.28.1" + "@statoscope/helpers": "npm:5.28.1" + "@statoscope/stats": "npm:5.28.1" + gzip-size: "npm:^6.0.0" + checksum: 6367df1c0d77a4f57dbbeb1fb7eb61a7eea2cd7b811a0bd02cddf95620b3cfc52b1a44e27bcc3836f98a4dd827737d27ca6ff5afdcdcffbe6f81dad336990e36 + languageName: node + linkType: hard + "@statoscope/stats-extension-custom-reports@npm:5.25.0": version: 5.25.0 resolution: "@statoscope/stats-extension-custom-reports@npm:5.25.0" @@ -3157,6 +3189,18 @@ __metadata: languageName: node linkType: hard +"@statoscope/stats-extension-custom-reports@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/stats-extension-custom-reports@npm:5.28.1" + dependencies: + "@statoscope/extensions": "npm:5.28.1" + "@statoscope/helpers": "npm:5.28.1" + "@statoscope/stats": "npm:5.28.1" + "@statoscope/types": "npm:5.28.1" + checksum: 4b7ca6cc9844633a75937d589b6d121217ce30b28a7982d8869bc37f027e36a283faccc6180996dae57047253059a6883c86077738280d173c8b5e45af7509d3 + languageName: node + linkType: hard + "@statoscope/stats-extension-package-info@npm:5.25.0": version: 5.25.0 resolution: "@statoscope/stats-extension-package-info@npm:5.25.0" @@ -3166,6 +3210,17 @@ __metadata: languageName: node linkType: hard +"@statoscope/stats-extension-package-info@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/stats-extension-package-info@npm:5.28.1" + dependencies: + "@statoscope/extensions": "npm:5.28.1" + "@statoscope/helpers": "npm:5.28.1" + "@statoscope/stats": "npm:5.28.1" + checksum: d06e7c80b2b552e63f114b93054ef491623da56ac8f01e84c155f8dc28d8ebb03b1af0c0e45c06ab3ee8ceb80327b1da8b72a1aaeea8eb49e3c15d74bdc81e6e + languageName: node + linkType: hard + "@statoscope/stats-extension-stats-validation-result@npm:5.25.0": version: 5.25.0 resolution: "@statoscope/stats-extension-stats-validation-result@npm:5.25.0" @@ -3178,40 +3233,53 @@ __metadata: languageName: node linkType: hard -"@statoscope/stats-validator-plugin-webpack@npm:5.26.2": - version: 5.26.2 - resolution: "@statoscope/stats-validator-plugin-webpack@npm:5.26.2" +"@statoscope/stats-extension-stats-validation-result@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/stats-extension-stats-validation-result@npm:5.28.1" dependencies: - "@statoscope/helpers": "npm:5.25.0" - "@statoscope/stats-extension-compressed": "npm:5.25.0" - "@statoscope/stats-validator": "npm:5.22.0" - "@statoscope/types": "npm:5.22.0" - "@statoscope/webpack-model": "npm:5.26.2" + "@statoscope/extensions": "npm:5.28.1" + "@statoscope/helpers": "npm:5.28.1" + "@statoscope/stats": "npm:5.28.1" + "@statoscope/types": "npm:5.28.1" + checksum: 245dcb2b03b2794ba1fb7343bda0b3ef6e95a9351a8acb68877852f6f83229e55976e4ba03b60cc2195469f597db0d4e95a221446cca7c8d3500e3c05f587509 + languageName: node + linkType: hard + +"@statoscope/stats-validator-plugin-webpack@npm:5.28.2": + version: 5.28.2 + resolution: "@statoscope/stats-validator-plugin-webpack@npm:5.28.2" + dependencies: + "@statoscope/helpers": "npm:5.28.1" + "@statoscope/stats-extension-compressed": "npm:5.28.1" + "@statoscope/stats-validator": "npm:5.28.1" + "@statoscope/types": "npm:5.28.1" + "@statoscope/webpack-model": "npm:5.28.2" + "@types/semver": "npm:^7.5.1" chalk: "npm:^4.1.2" - semver: "npm:^7.3.7" - checksum: 4073d8c811b8c9fa765b354af66c1a58509fabee70712743f887ae9e225845d634505caf3d881fd5214b83f5b40b43a67d7d97bb7b77d5cafca4751e005e4746 + semver: "npm:^7.5.4" + checksum: a025ae2e4238fec8144ab7b7eef4d69888123de3c61ed50ff90e8b18f4db90a84ced406795dce0d9e09ecf262adb7c27e50f824a975b7cf869c7cf8aeea32106 languageName: node linkType: hard -"@statoscope/stats-validator-reporter-console@npm:5.22.0": - version: 5.22.0 - resolution: "@statoscope/stats-validator-reporter-console@npm:5.22.0" +"@statoscope/stats-validator-reporter-console@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/stats-validator-reporter-console@npm:5.28.1" dependencies: - "@statoscope/types": "npm:5.22.0" + "@statoscope/types": "npm:5.28.1" chalk: "npm:^4.1.2" - checksum: fc28b4558e151ad34a8e9ea17bd028adb6e9a35d57afd837e8a8354331f743e37cc191180fe90a24ad8aec4989c4b69413fb3e9064f3ac9b35c6c30eeaf92697 + checksum: b4fcfde754c3a874f15ae423db8418f5a7142bd08c1f68d9c2e51afaa2a76be59fa2d31bdf387449e4584e589fb5939b5cc1cc3080a9da90380dff9c43b72c7d languageName: node linkType: hard -"@statoscope/stats-validator@npm:5.22.0": - version: 5.22.0 - resolution: "@statoscope/stats-validator@npm:5.22.0" +"@statoscope/stats-validator@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/stats-validator@npm:5.28.1" dependencies: "@discoveryjs/json-ext": "npm:^0.5.7" - "@statoscope/config": "npm:5.22.0" - "@statoscope/stats": "npm:5.14.1" - "@statoscope/types": "npm:5.22.0" - checksum: fd3f83120ce4b098c02463fc64ea7aabb2d2b90fd76aef5e2c8d8a3f53c38adb94fde77cfd21dbcb9e8b4bd51a40246ceeac2b5d60d8274e5679392042134cbe + "@statoscope/config": "npm:5.28.1" + "@statoscope/stats": "npm:5.28.1" + "@statoscope/types": "npm:5.28.1" + checksum: d478246f9243cae2d5c0cb7384cacdf7fc08273873fa29ac8a46719c43746cf8dfe85fe251703856108724a65d43453222f9b92b4eda4c2db401ec2ed4727583 languageName: node linkType: hard @@ -3222,6 +3290,13 @@ __metadata: languageName: node linkType: hard +"@statoscope/stats@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/stats@npm:5.28.1" + checksum: 1be9838120f1b14306dc508ce2a01022c93ce25994c883b9c1cf50060e122e61c2ea09d8fc47df68ef6f7aa93490cf2a8f1912cdc55de6b07a2622e5a0d184f4 + languageName: node + linkType: hard + "@statoscope/types@npm:5.22.0": version: 5.22.0 resolution: "@statoscope/types@npm:5.22.0" @@ -3231,6 +3306,15 @@ __metadata: languageName: node linkType: hard +"@statoscope/types@npm:5.28.1": + version: 5.28.1 + resolution: "@statoscope/types@npm:5.28.1" + dependencies: + "@statoscope/stats": "npm:5.28.1" + checksum: e1207adc7a166507ce1a53a42bcc1d6ee11578c08fb52ca9e48e45c303c5af6a3b03c7853f1e64c7795b3e0d7169cc39e710d21546bd3cb678702bbf85d5e61b + languageName: node + linkType: hard + "@statoscope/webpack-model@npm:5.25.1": version: 5.25.1 resolution: "@statoscope/webpack-model@npm:5.25.1" @@ -3248,20 +3332,22 @@ __metadata: languageName: node linkType: hard -"@statoscope/webpack-model@npm:5.26.2": - version: 5.26.2 - resolution: "@statoscope/webpack-model@npm:5.26.2" +"@statoscope/webpack-model@npm:5.28.2": + version: 5.28.2 + resolution: "@statoscope/webpack-model@npm:5.28.2" dependencies: - "@statoscope/extensions": "npm:5.14.1" - "@statoscope/helpers": "npm:5.25.0" - "@statoscope/stats": "npm:5.14.1" - "@statoscope/stats-extension-compressed": "npm:5.25.0" - "@statoscope/stats-extension-custom-reports": "npm:5.25.0" - "@statoscope/stats-extension-package-info": "npm:5.25.0" - "@statoscope/stats-extension-stats-validation-result": "npm:5.25.0" - "@statoscope/types": "npm:5.22.0" + "@statoscope/extensions": "npm:5.28.1" + "@statoscope/helpers": "npm:5.28.1" + "@statoscope/stats": "npm:5.28.1" + "@statoscope/stats-extension-compressed": "npm:5.28.1" + "@statoscope/stats-extension-custom-reports": "npm:5.28.1" + "@statoscope/stats-extension-package-info": "npm:5.28.1" + "@statoscope/stats-extension-stats-validation-result": "npm:5.28.1" + "@statoscope/types": "npm:5.28.1" + "@types/md5": "npm:^2.3.2" + "@types/webpack": "npm:^5.0.0" md5: "npm:^2.3.0" - checksum: 422450a2fd3f9ac507f59e08a62b40bdac94e9c1fe862d7e278ebb547c5e65271e003bd6042ec83c48f8921e7acacc0e9258dc7447735b6468e8f6bf26daccb1 + checksum: 419245dd3948c80063c63ac61782c26319c976992ad84049dca93f4feebdbea9e0922d36bc01315b50e5b8355a53315665b40a6312a8b1dfea21482c1327cd55 languageName: node linkType: hard @@ -5065,6 +5151,13 @@ __metadata: languageName: node linkType: hard +"@types/md5@npm:^2.3.2": + version: 2.3.5 + resolution: "@types/md5@npm:2.3.5" + checksum: a86baf0521006e3072488bd79089b84831780866102e5e4b4f7afabfab17e0270a3791f3331776b73efb2cc9317efd56a334fd3d2698c7929e9b18593ca3fd39 + languageName: node + linkType: hard + "@types/mdast@npm:^3.0.0": version: 3.0.12 resolution: "@types/mdast@npm:3.0.12" @@ -5251,6 +5344,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7.5.1": + version: 7.5.8 + resolution: "@types/semver@npm:7.5.8" + checksum: 3496808818ddb36deabfe4974fd343a78101fa242c4690044ccdc3b95dcf8785b494f5d628f2f47f38a702f8db9c53c67f47d7818f2be1b79f2efb09692e1178 + languageName: node + linkType: hard + "@types/send@npm:*": version: 0.17.1 resolution: "@types/send@npm:0.17.1" @@ -5342,6 +5442,17 @@ __metadata: languageName: node linkType: hard +"@types/webpack@npm:^5.0.0": + version: 5.28.5 + resolution: "@types/webpack@npm:5.28.5" + dependencies: + "@types/node": "npm:*" + tapable: "npm:^2.2.0" + webpack: "npm:^5" + checksum: 14359d9ccecef7ef1ea271c00baec5337213c7fda63a34c61b9e519505b3928d0807cdbb5b1172d1994e1179920b89c57eaf2cbf64599958b67cd70720ac2a9b + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -5349,15 +5460,6 @@ __metadata: languageName: node linkType: hard -"@types/yargs@npm:^15.0.3": - version: 15.0.15 - resolution: "@types/yargs@npm:15.0.15" - dependencies: - "@types/yargs-parser": "npm:*" - checksum: cb5a4bc8b8cb3f52099c950c605cf9e5702cd4b1731c125e28f757f247253345af59c9101887c94a3add67dbf283bca5a3b38e31b9b3cdaec3bf0b1d6b6ae0b9 - languageName: node - linkType: hard - "@types/yargs@npm:^16.0.0": version: 16.0.5 resolution: "@types/yargs@npm:16.0.5" @@ -5613,7 +5715,7 @@ __metadata: "@types/webpack-env": "npm:1.18.1" "@v4fire/cli": "npm:1.6.0" "@v4fire/config": "npm:1.0.0" - "@v4fire/core": "github:V4Fire/Core#v4" + "@v4fire/core": "github:V4Fire/Core#speedup" "@v4fire/design-system": "npm:1.23.0" "@v4fire/linters": "git+https://github.com/v4fire/linters#rework_rules" "@v4fire/storybook": "npm:0.8.0" @@ -5887,9 +5989,9 @@ __metadata: languageName: node linkType: hard -"@v4fire/core@github:V4Fire/Core#v4": - version: 4.0.0-alpha.34 - resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=c595aa280742ffe539801425834e6192c7404689" +"@v4fire/core@github:V4Fire/Core#speedup": + version: 4.0.0-alpha.43 + resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=11e6de3aaef966c494a3ed1bcc4fb724bf47c995" dependencies: "@babel/core": "npm:7.17.5" "@babel/helper-module-transforms": "npm:7.16.7" @@ -6032,7 +6134,7 @@ __metadata: optional: true xhr2: optional: true - checksum: efd868f8ac61ad318be35872f60efde63103a0295336431ba116b0c4ce36aafad2ab8d0a1bd4143a781dba34d95f8ac50c2eb7034b2dbd7fa1deaa94b46fd0a7 + checksum: a6d8ba11c0a9403f411399cd98162e812ebeda67f1cd9b2ea6cf3200e0c85f82f3c2a35235a13a43402f0b107fc0de45fe8034346fe1fe5e154431accba32727 languageName: node linkType: hard @@ -6060,13 +6162,13 @@ __metadata: linkType: hard "@v4fire/linters@git+https://github.com/v4fire/linters#rework_rules": - version: 2.4.0 - resolution: "@v4fire/linters@https://github.com/v4fire/linters.git#commit=e3c44f597c7348e82319fc3f8e7e32d1632187be" + version: 2.8.0 + resolution: "@v4fire/linters@https://github.com/v4fire/linters.git#commit=f7f700fee3c9446bddb72b3d6a4ab2a67ade25c4" dependencies: "@babel/helper-validator-identifier": "npm:7.18.6" "@eslint/eslintrc": "npm:1.3.0" - "@statoscope/stats-validator-plugin-webpack": "npm:5.26.2" - "@statoscope/stats-validator-reporter-console": "npm:5.22.0" + "@statoscope/stats-validator-plugin-webpack": "npm:5.28.2" + "@statoscope/stats-validator-reporter-console": "npm:5.28.1" "@typescript-eslint/eslint-plugin": "npm:6.7.0" "@typescript-eslint/parser": "npm:6.7.0" "@v4fire/eslint-plugin": "npm:1.0.0-alpha.14" @@ -6074,14 +6176,13 @@ __metadata: eslint: "npm:8.49.0" eslint-import-resolver-typescript: "npm:3.5.5" eslint-plugin-deprecation: "npm:1.4.1" + eslint-plugin-enchanted-curly: "npm:1.1.0" eslint-plugin-header: "npm:3.1.1" eslint-plugin-import: "npm:2.27.5" eslint-plugin-jsdoc: "npm:44.2.5" eslint-plugin-optimize-regex: "npm:1.2.1" eslint-plugin-playwright: "npm:0.12.0" eslint-plugin-storybook: "npm:0.6.12" - stlint: "npm:1.0.65" - stlint-v4fire: "npm:1.0.38" peerDependencies: jest: ^29.5.0 jest-runner-eslint: ^2.0.0 @@ -6089,7 +6190,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 5cdcddb7b3ba6c76015c58c6117818ea19fb45682fd5add7d64b778f6a7feec78c18479637bdea04d63d1fb37cf4e80d7e3a2081c47d74f30f2e48b9611d8da8 + checksum: a209bbc5e76b08f8a7306ddf6f1435caafcb452893b543548e3622c108ec2dcdabc37275ee73b0e56eaea073a724461e68d425aa88f123b68abdba18fb4448a4 languageName: node linkType: hard @@ -6775,13 +6876,6 @@ __metadata: languageName: node linkType: hard -"ansi-regex@npm:^4.1.0": - version: 4.1.1 - resolution: "ansi-regex@npm:4.1.1" - checksum: b1a6ee44cb6ecdabaa770b2ed500542714d4395d71c7e5c25baa631f680fb2ad322eb9ba697548d498a6fd366949fc8b5bfcf48d49a32803611f648005b01888 - languageName: node - linkType: hard - "ansi-regex@npm:^5.0.1": version: 5.0.1 resolution: "ansi-regex@npm:5.0.1" @@ -6803,7 +6897,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1": +"ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" dependencies: @@ -7310,7 +7404,7 @@ __metadata: languageName: node linkType: hard -"async@npm:^2.6.2, async@npm:^2.6.3": +"async@npm:^2.6.2": version: 2.6.4 resolution: "async@npm:2.6.4" dependencies: @@ -9047,13 +9141,6 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^2.0.0": - version: 2.0.0 - resolution: "ci-info@npm:2.0.0" - checksum: 3b374666a85ea3ca43fa49aa3a048d21c9b475c96eb13c133505d2324e7ae5efd6a454f41efe46a152269e9b6a00c9edbe63ec7fa1921957165aae16625acd67 - languageName: node - linkType: hard - "ci-info@npm:^3.2.0": version: 3.8.0 resolution: "ci-info@npm:3.8.0" @@ -9165,17 +9252,6 @@ __metadata: languageName: node linkType: hard -"cliui@npm:^5.0.0": - version: 5.0.0 - resolution: "cliui@npm:5.0.0" - dependencies: - string-width: "npm:^3.1.0" - strip-ansi: "npm:^5.2.0" - wrap-ansi: "npm:^5.1.0" - checksum: 381264fcc3c8316b77b378ce5471ff9a1974d1f6217e0be8f4f09788482b3e6f7c0894eb21e0a86eab4ce0c68426653a407226dd51997306cb87f734776f5fdc - languageName: node - linkType: hard - "cliui@npm:^6.0.0": version: 6.0.0 resolution: "cliui@npm:6.0.0" @@ -9365,13 +9441,6 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:~0.5.0": - version: 0.5.3 - resolution: "color-convert@npm:0.5.3" - checksum: 00dc4256c07ed8760d7bbba234ff969c139eb964fe165853696852001002695c492e327d83ddb7a8cad8d27b49fa543d001328928c12474ee8ecb335bf5f2eb4 - languageName: node - linkType: hard - "color-name@npm:1.1.3": version: 1.1.3 resolution: "color-name@npm:1.1.3" @@ -9452,16 +9521,6 @@ __metadata: languageName: node linkType: hard -"columnify@npm:^1.5.4": - version: 1.6.0 - resolution: "columnify@npm:1.6.0" - dependencies: - strip-ansi: "npm:^6.0.1" - wcwidth: "npm:^1.0.0" - checksum: ab742cc646c07293db603f7a4387ca9d46d32beaaba0a08008c2f31f30042e6e5a940096fb7d2d432495597ed3d5c5fe07a5bacd55e4ac24a768d344a47dd678 - languageName: node - linkType: hard - "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -9548,13 +9607,6 @@ __metadata: languageName: node linkType: hard -"compare-versions@npm:^3.6.0": - version: 3.6.0 - resolution: "compare-versions@npm:3.6.0" - checksum: 7492a50cdaa2c27f5254eee7c4b38856e1c164991bab3d98d7fd067fe4b570d47123ecb92523b78338be86aa221668fd3868bfe8caa5587dc3ebbe1a03d52b5d - languageName: node - linkType: hard - "component-emitter@npm:^1.2.1": version: 1.3.0 resolution: "component-emitter@npm:1.3.0" @@ -11176,13 +11228,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:^7.0.1": - version: 7.0.3 - resolution: "emoji-regex@npm:7.0.3" - checksum: 9159b2228b1511f2870ac5920f394c7e041715429a68459ebe531601555f11ea782a8e1718f969df2711d38c66268174407cbca57ce36485544f695c2dfdc96e - languageName: node - linkType: hard - "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -11636,7 +11681,7 @@ __metadata: languageName: node linkType: hard -"escaper@npm:3.0.6, escaper@npm:^3.0.3": +"escaper@npm:3.0.6": version: 3.0.6 resolution: "escaper@npm:3.0.6" checksum: 607f7c5ec892526d653d44851d0df8c51a7e0cbab768baf1bee5827569b3d3b39c75b24ccb62cdefd48458100cb11d5bb25b45bb20419dae96799d3b963327d9 @@ -11724,6 +11769,13 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-enchanted-curly@npm:1.1.0": + version: 1.1.0 + resolution: "eslint-plugin-enchanted-curly@npm:1.1.0" + checksum: 361045cee6e65a52a956460c154d62566c9d4eb4bf8ce827ada282c4fdb77185c0d3f6acdce79db4346c071c7461e181be50b05f2cdb745687887df139b78fd3 + languageName: node + linkType: hard + "eslint-plugin-header@npm:3.1.1": version: 3.1.1 resolution: "eslint-plugin-header@npm:3.1.1" @@ -12843,15 +12895,6 @@ __metadata: languageName: node linkType: hard -"find-versions@npm:^4.0.0": - version: 4.0.0 - resolution: "find-versions@npm:4.0.0" - dependencies: - semver-regex: "npm:^3.1.2" - checksum: 2b4c749dc33e3fa73a457ca4df616ac13b4b32c53f6297bc862b0814d402a6cfec93a0d308d5502eeb47f2c125906e0f861bf01b756f08395640892186357711 - languageName: node - linkType: hard - "findup-sync@npm:0.4.2": version: 0.4.2 resolution: "findup-sync@npm:0.4.2" @@ -14793,27 +14836,6 @@ __metadata: languageName: node linkType: hard -"husky@npm:^4.2.3": - version: 4.3.8 - resolution: "husky@npm:4.3.8" - dependencies: - chalk: "npm:^4.0.0" - ci-info: "npm:^2.0.0" - compare-versions: "npm:^3.6.0" - cosmiconfig: "npm:^7.0.0" - find-versions: "npm:^4.0.0" - opencollective-postinstall: "npm:^2.0.2" - pkg-dir: "npm:^5.0.0" - please-upgrade-node: "npm:^3.2.0" - slash: "npm:^3.0.0" - which-pm-runs: "npm:^1.0.0" - bin: - husky-run: bin/run.js - husky-upgrade: lib/upgrader/bin.js - checksum: bf525b1133ac68131a50acfea1b6348ed4b3230ff471492852edd97f3038e858ccf075eac431db47c27d928638a108eef70bc3940f289f5a3ac0eca20808969a - languageName: node - linkType: hard - "iconv-lite@npm:0.4.24": version: 0.4.24 resolution: "iconv-lite@npm:0.4.24" @@ -15481,13 +15503,6 @@ __metadata: languageName: node linkType: hard -"is-fullwidth-code-point@npm:^2.0.0": - version: 2.0.0 - resolution: "is-fullwidth-code-point@npm:2.0.0" - checksum: eef9c6e15f68085fec19ff6a978a6f1b8f48018fd1265035552078ee945573594933b09bbd6f562553e2a241561439f1ef5339276eba68d272001343084cfab8 - languageName: node - linkType: hard - "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -16664,6 +16679,15 @@ __metadata: languageName: node linkType: hard +"jora@npm:1.0.0-beta.8": + version: 1.0.0-beta.8 + resolution: "jora@npm:1.0.0-beta.8" + dependencies: + "@discoveryjs/natural-compare": "npm:^1.0.0" + checksum: 0b3f774209a149af2d2b98599c41394c8614c1b2e1aeede6bbbae4f9961cf995b770bfd8920010a60491efe409105b37f62d5f4116c5ec1091ecbe3a4e706c10 + languageName: node + linkType: hard + "jora@npm:^1.0.0-beta.7": version: 1.0.0-beta.7 resolution: "jora@npm:1.0.0-beta.7" @@ -18706,13 +18730,6 @@ __metadata: languageName: node linkType: hard -"native-require@npm:^1.1.4": - version: 1.1.4 - resolution: "native-require@npm:1.1.4" - checksum: 6bf6465524d0d620aed1c8422655687030b596d2f36ae89c47d1a4651ca5fe6a3f7f1dd34adb027298607d48c683d68f663b2a395845bd44f069d1a38f298536 - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -18909,13 +18926,6 @@ __metadata: languageName: node linkType: hard -"node-watch@npm:^0.6.3": - version: 0.6.4 - resolution: "node-watch@npm:0.6.4" - checksum: 033b81ca48a19c4f21ee7b46d649f0e750883957f689a885c61855fd363d774793263a7ee526fbb5a33d0fc45129776c13870fbadd06f9cc0aaee12fef994646 - languageName: node - linkType: hard - "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -19347,15 +19357,6 @@ __metadata: languageName: node linkType: hard -"opencollective-postinstall@npm:^2.0.2": - version: 2.0.3 - resolution: "opencollective-postinstall@npm:2.0.3" - bin: - opencollective-postinstall: index.js - checksum: 69d63778087cd10c9d707d9ed360556780cfdd0cd6241ded0e26632f467f1d5a064f4a9aec19a30c187770c17adba034d988f7684b226f3a73e79f44e73fab0e - languageName: node - linkType: hard - "opter@npm:^1.1.0": version: 1.1.0 resolution: "opter@npm:1.1.0" @@ -19715,15 +19716,6 @@ __metadata: languageName: node linkType: hard -"parse-color@npm:^1.0.0": - version: 1.0.0 - resolution: "parse-color@npm:1.0.0" - dependencies: - color-convert: "npm:~0.5.0" - checksum: 9f988462af30929acbbce135286ce20dc2fb8efe48fd0693ed8bfd7da3a60e2465cffff6d35047fe94fd7863b43159bf41b377a9982939b7c65fdba1e30af24a - languageName: node - linkType: hard - "parse-entities@npm:^2.0.0": version: 2.0.0 resolution: "parse-entities@npm:2.0.0" @@ -20129,15 +20121,6 @@ __metadata: languageName: node linkType: hard -"please-upgrade-node@npm:^3.2.0": - version: 3.2.0 - resolution: "please-upgrade-node@npm:3.2.0" - dependencies: - semver-compare: "npm:^1.0.0" - checksum: d87c41581a2a022fbe25965a97006238cd9b8cbbf49b39f78d262548149a9d30bd2bdf35fec3d810e0001e630cd46ef13c7e19c389dea8de7e64db271a2381bb - languageName: node - linkType: hard - "plugin-error@npm:^0.1.2": version: 0.1.2 resolution: "plugin-error@npm:0.1.2" @@ -20830,15 +20813,6 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^1.19.1": - version: 1.19.1 - resolution: "prettier@npm:1.19.1" - bin: - prettier: ./bin-prettier.js - checksum: 21d245fe788d1123fd7df63a3759f7807d8c0bb7aaa2f4f02cc055998c3e1f92d7c614b6e2749a936c527cdbbb0f3c869bd9723eb32c5232b0fab9fac04b455d - languageName: node - linkType: hard - "prettier@npm:^2.8.0": version: 2.8.8 resolution: "prettier@npm:2.8.8" @@ -22224,13 +22198,6 @@ __metadata: languageName: node linkType: hard -"semver-compare@npm:^1.0.0": - version: 1.0.0 - resolution: "semver-compare@npm:1.0.0" - checksum: 75f9c7a7786d1756f64b1429017746721e07bd7691bdad6368f7643885d3a98a27586777e9699456564f4844b407e9f186cc1d588a3f9c0be71310e517e942c3 - languageName: node - linkType: hard - "semver-greatest-satisfied-range@npm:^1.1.0": version: 1.1.0 resolution: "semver-greatest-satisfied-range@npm:1.1.0" @@ -22247,13 +22214,6 @@ __metadata: languageName: node linkType: hard -"semver-regex@npm:^3.1.2": - version: 3.1.4 - resolution: "semver-regex@npm:3.1.4" - checksum: 3962105908e326aa2cd5c851a2f6d4cc7340d1b06560afc35cd5348d9fa5b1cc0ac0cad7e7cef2072bc12b992c5ae654d9e8d355c19d75d4216fced3b6c5d8a7 - languageName: node - linkType: hard - "semver-truncate@npm:^1.1.2": version: 1.1.2 resolution: "semver-truncate@npm:1.1.2" @@ -22951,40 +22911,6 @@ __metadata: languageName: node linkType: hard -"stlint-v4fire@npm:1.0.38": - version: 1.0.38 - resolution: "stlint-v4fire@npm:1.0.38" - dependencies: - parse-color: "npm:^1.0.0" - stlint: "npm:^1.0.62" - stylus: "npm:^0.54.7" - checksum: bb3b6baa5a425981607be62a8ee12b5ba57b28832cf702fc370e48ff453df7c5428708e2d2165197edaeef4431f2669ff3b3b23ed71110cd2ff174477128fb5b - languageName: node - linkType: hard - -"stlint@npm:1.0.65, stlint@npm:^1.0.62": - version: 1.0.65 - resolution: "stlint@npm:1.0.65" - dependencies: - "@types/yargs": "npm:^15.0.3" - async: "npm:^2.6.3" - chalk: "npm:^2.4.2" - columnify: "npm:^1.5.4" - escaper: "npm:^3.0.3" - glob: "npm:^7.1.6" - husky: "npm:^4.2.3" - native-require: "npm:^1.1.4" - node-watch: "npm:^0.6.3" - prettier: "npm:^1.19.1" - strip-json-comments: "npm:^2.0.1" - stylus: "npm:^0.54.7" - yargs: "npm:^13.3.0" - bin: - stlint: bin/stlint - checksum: 11266ed1345caef5c873a68a6ad536ea2505c0033a78606b366a9e802f47d10f622d0520ad45594bf7f3f4acd317fb68c302b8ad06113548ab7a215bc9424119 - languageName: node - linkType: hard - "stop-iteration-iterator@npm:^1.0.0": version: 1.0.0 resolution: "stop-iteration-iterator@npm:1.0.0" @@ -23089,17 +23015,6 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^3.0.0, string-width@npm:^3.1.0": - version: 3.1.0 - resolution: "string-width@npm:3.1.0" - dependencies: - emoji-regex: "npm:^7.0.1" - is-fullwidth-code-point: "npm:^2.0.0" - strip-ansi: "npm:^5.1.0" - checksum: 57f7ca73d201682816d573dc68bd4bb8e1dff8dc9fcf10470fdfc3474135c97175fec12ea6a159e67339b41e86963112355b64529489af6e7e70f94a7caf08b2 - languageName: node - linkType: hard - "string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" @@ -23187,15 +23102,6 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^5.0.0, strip-ansi@npm:^5.1.0, strip-ansi@npm:^5.2.0": - version: 5.2.0 - resolution: "strip-ansi@npm:5.2.0" - dependencies: - ansi-regex: "npm:^4.1.0" - checksum: bdb5f76ade97062bd88e7723aa019adbfacdcba42223b19ccb528ffb9fb0b89a5be442c663c4a3fb25268eaa3f6ea19c7c3fbae830bd1562d55adccae1fcec46 - languageName: node - linkType: hard - "strip-ansi@npm:^7.0.1": version: 7.1.0 resolution: "strip-ansi@npm:7.1.0" @@ -23276,7 +23182,7 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:^2.0.0, strip-json-comments@npm:^2.0.1, strip-json-comments@npm:~2.0.1": +"strip-json-comments@npm:^2.0.0, strip-json-comments@npm:~2.0.1": version: 2.0.1 resolution: "strip-json-comments@npm:2.0.1" checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 @@ -23366,7 +23272,7 @@ __metadata: languageName: node linkType: hard -"stylus@npm:0.54.8, stylus@npm:^0.54.7": +"stylus@npm:0.54.8": version: 0.54.8 resolution: "stylus@npm:0.54.8" dependencies: @@ -25464,7 +25370,7 @@ __metadata: languageName: node linkType: hard -"wcwidth@npm:^1.0.0, wcwidth@npm:^1.0.1": +"wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1" dependencies: @@ -25746,13 +25652,6 @@ __metadata: languageName: node linkType: hard -"which-pm-runs@npm:^1.0.0": - version: 1.1.0 - resolution: "which-pm-runs@npm:1.1.0" - checksum: 39a56ee50886fb33ec710e3b36dc9fe3d0096cac44850d9ca0c6186c4cb824d6c8125f013e0562e7c94744e1e8e4a6ab695592cdb12555777c7a4368143d822c - languageName: node - linkType: hard - "which-typed-array@npm:^1.1.11, which-typed-array@npm:^1.1.2, which-typed-array@npm:^1.1.9": version: 1.1.11 resolution: "which-typed-array@npm:1.1.11" @@ -25853,17 +25752,6 @@ __metadata: languageName: node linkType: hard -"wrap-ansi@npm:^5.1.0": - version: 5.1.0 - resolution: "wrap-ansi@npm:5.1.0" - dependencies: - ansi-styles: "npm:^3.2.0" - string-width: "npm:^3.0.0" - strip-ansi: "npm:^5.0.0" - checksum: f02bbbd13f40169f3d69b8c95126c1d2a340e6f149d04125527c3d501d74a304a434f4329a83bfdc3b9fdb82403e9ae0cdd7b83a99f0da0d5a7e544f6b709914 - languageName: node - linkType: hard - "wrap-ansi@npm:^6.2.0": version: 6.2.0 resolution: "wrap-ansi@npm:6.2.0" @@ -26073,16 +25961,6 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^13.1.2": - version: 13.1.2 - resolution: "yargs-parser@npm:13.1.2" - dependencies: - camelcase: "npm:^5.0.0" - decamelize: "npm:^1.2.0" - checksum: 89a84fbb32827832a1d34f596f5efe98027c398af731728304a920c2f9ba03071c694418723df16882ebb646ddb72a8fb1c9567552afcbc2f268e86c4faea5a8 - languageName: node - linkType: hard - "yargs-parser@npm:^18.1.2": version: 18.1.3 resolution: "yargs-parser@npm:18.1.3" @@ -26118,24 +25996,6 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^13.3.0": - version: 13.3.2 - resolution: "yargs@npm:13.3.2" - dependencies: - cliui: "npm:^5.0.0" - find-up: "npm:^3.0.0" - get-caller-file: "npm:^2.0.1" - require-directory: "npm:^2.1.1" - require-main-filename: "npm:^2.0.0" - set-blocking: "npm:^2.0.0" - string-width: "npm:^3.0.0" - which-module: "npm:^2.0.0" - y18n: "npm:^4.0.0" - yargs-parser: "npm:^13.1.2" - checksum: 608ba2e62ac2c7c4572b9c6f7a2d3ef76e2deaad8c8082788ed29ae3ef33e9f68e087f07eb804ed5641de2bc4eab977405d3833b1d11ae8dbbaf5847584d96be - languageName: node - linkType: hard - "yargs@npm:^15.0.2, yargs@npm:^15.3.1": version: 15.4.1 resolution: "yargs@npm:15.4.1" From 29f652cf1fb7038135e5e38e84d052cf02962a55 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 15:39:28 +0300 Subject: [PATCH 068/334] chore: fixed eslint --- tests/helpers/network/interceptor/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/helpers/network/interceptor/index.ts b/tests/helpers/network/interceptor/index.ts index c35f33010d..d7fc185c26 100644 --- a/tests/helpers/network/interceptor/index.ts +++ b/tests/helpers/network/interceptor/index.ts @@ -132,7 +132,6 @@ export default class RequestInterceptor { * @param at - the index of the request (starting from 0) */ request(at: number): CanUndef { - // eslint-disable-next-line no-restricted-syntax const request: CanUndef = this.calls.at(at)?.[0]?.request(); if (request == null) { From 132c42ee77256ea154ad16d90a863a9f20d8b954 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 15:39:44 +0300 Subject: [PATCH 069/334] chore: updated dependencies --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index c6e9f02ad1..03376ef08a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5991,7 +5991,7 @@ __metadata: "@v4fire/core@github:V4Fire/Core#speedup": version: 4.0.0-alpha.43 - resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=11e6de3aaef966c494a3ed1bcc4fb724bf47c995" + resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=4cc4500ac07bc7198d6258701626b338ece4a301" dependencies: "@babel/core": "npm:7.17.5" "@babel/helper-module-transforms": "npm:7.16.7" @@ -6134,7 +6134,7 @@ __metadata: optional: true xhr2: optional: true - checksum: a6d8ba11c0a9403f411399cd98162e812ebeda67f1cd9b2ea6cf3200e0c85f82f3c2a35235a13a43402f0b107fc0de45fe8034346fe1fe5e154431accba32727 + checksum: 7eae4a85334424bcf4ab1c65a0ef4cdb8806f709f683c3483cd2f685264ab7ae59090b8c89f0798f6838481de9dd09b3b4feec45513c23f52405e0fe131c11f3 languageName: node linkType: hard From a6753b080f18c73af83be8615b265ebc95505ecf Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 15:45:20 +0300 Subject: [PATCH 070/334] chore: use Array.toArray instead of Array.concat --- src/components/base/b-dynamic-page/b-dynamic-page.ts | 4 ++-- src/components/base/b-list/b-list.ts | 4 ++-- src/components/base/b-tree/b-tree.ts | 2 +- .../base/b-virtual-scroll/modules/chunk-request.ts | 2 +- src/components/directives/bind-with/helpers.ts | 2 +- src/components/directives/image/helpers.ts | 3 +-- src/components/directives/in-view/index.ts | 4 ++-- src/components/directives/on-resize/index.ts | 4 ++-- src/components/form/b-input/validators/index.ts | 2 ++ src/components/form/b-select/b-select.ss | 2 +- src/components/form/b-select/b-select.ts | 6 +++--- src/components/form/b-select/test/helpers.ts | 2 +- src/components/friends/async-render/iterate.ts | 5 ++--- src/components/friends/daemons/helpers.ts | 4 ++-- src/components/friends/data-provider/request.ts | 2 +- src/components/friends/provide/classes.ts | 2 +- src/components/friends/state/helpers.ts | 3 +-- src/components/friends/vdom/vnode.ts | 6 +++--- src/components/super/i-block/modules/listeners/index.ts | 7 +++---- src/components/super/i-input-text/mask/handlers/input.ts | 2 +- src/components/traits/i-active-items/i-active-items.ts | 4 ++-- src/components/traits/i-control-list/i-control-list.ts | 3 +-- src/core/async/index.ts | 2 +- src/core/component/decorators/factory.ts | 6 +++--- src/core/component/directives/render/index.ts | 3 +-- src/core/component/engines/vue3/render.ts | 2 +- src/core/component/event/component.ts | 5 ++--- src/core/component/meta/fill.ts | 2 +- src/core/component/reflect/mod.ts | 2 +- src/core/component/render/wrappers.ts | 2 +- 30 files changed, 47 insertions(+), 52 deletions(-) diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index 17fa9d67f4..d656b20cde 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -463,7 +463,7 @@ export default class bDynamicPage extends iDynamicPage { return loopbackStrategy; } - } else if (Object.isRegExp(exclude) ? exclude.test(page) : Array.concat([], exclude).includes(page)) { + } else if (Object.isRegExp(exclude) ? exclude.test(page) : Array.toArray(exclude).includes(page)) { return loopbackStrategy; } } @@ -509,7 +509,7 @@ export default class bDynamicPage extends iDynamicPage { }; } - if (Object.isRegExp(include) ? !include.test(page) : !Array.concat([], include).includes(page)) { + if (Object.isRegExp(include) ? !include.test(page) : !Array.toArray(include).includes(page)) { return loopbackStrategy; } } diff --git a/src/components/base/b-list/b-list.ts b/src/components/base/b-list/b-list.ts index 13002d8c7c..b7cacc090b 100644 --- a/src/components/base/b-list/b-list.ts +++ b/src/components/base/b-list/b-list.ts @@ -228,7 +228,7 @@ class bList extends iListProps implements iVisible, iWidth, iActiveItems { } SyncPromise.resolve(this.activeElement).then((selectedElement) => { - Array.concat([], selectedElement).forEach((el) => setActiveMod.call(this, el, true)); + Array.toArray(selectedElement).forEach((el) => setActiveMod.call(this, el, true)); }, stderr); } @@ -253,7 +253,7 @@ class bList extends iListProps implements iVisible, iWidth, iActiveItems { if ($b != null) { SyncPromise.resolve(activeElement).then((activeElement) => { - const els = Array.concat([], activeElement); + const els = Array.toArray(activeElement); els.forEach((el) => { const diff --git a/src/components/base/b-tree/b-tree.ts b/src/components/base/b-tree/b-tree.ts index a35c0be83b..806ae48c84 100644 --- a/src/components/base/b-tree/b-tree.ts +++ b/src/components/base/b-tree/b-tree.ts @@ -263,7 +263,7 @@ class bTree extends iTreeProps implements iActiveItems, iFoldable { // Activate current active nodes SyncPromise.resolve(this.activeElement).then((activeElement) => { - Array.concat([], activeElement).forEach((activeElement) => setActiveMod.call(top, activeElement, true)); + Array.toArray(activeElement).forEach((activeElement) => setActiveMod.call(top, activeElement, true)); }).catch(stderr); return true; diff --git a/src/components/base/b-virtual-scroll/modules/chunk-request.ts b/src/components/base/b-virtual-scroll/modules/chunk-request.ts index 631d016932..d288388ac3 100644 --- a/src/components/base/b-virtual-scroll/modules/chunk-request.ts +++ b/src/components/base/b-virtual-scroll/modules/chunk-request.ts @@ -277,7 +277,7 @@ export default class ChunkRequest extends Friend { this.data = this.data.concat(data); this.pendingData = this.pendingData.concat(data); - this.currentAccumulatedData = Array.concat(this.currentAccumulatedData ?? [], data); + this.currentAccumulatedData = Array.toArray(this.currentAccumulatedData, data); ctx.emit('dbChange', {...v, data: this.data}); this.shouldStopRequest(this.ctx.getCurrentDataState()); diff --git a/src/components/directives/bind-with/helpers.ts b/src/components/directives/bind-with/helpers.ts index 0ac3a908f0..75adb1633a 100644 --- a/src/components/directives/bind-with/helpers.ts +++ b/src/components/directives/bind-with/helpers.ts @@ -59,7 +59,7 @@ export function bindListenerToElement( group: new RegExp(`:${id}`) }); - Array.concat([], listener).forEach((listener: Listener) => { + Array.toArray(listener).forEach((listener: Listener) => { const group = { group: `${listener.group ?? ''}:${id}` }; diff --git a/src/components/directives/image/helpers.ts b/src/components/directives/image/helpers.ts index 7581292fe4..164988c3e7 100644 --- a/src/components/directives/image/helpers.ts +++ b/src/components/directives/image/helpers.ts @@ -176,8 +176,7 @@ export function createPictureElement( const picture: VNode = create('picture'); - picture.children = Array.concat( - [], + picture.children = Array.toArray( createSourceElements(imageParams, commonParams).toVNode(create), createImgElement(imageParams, commonParams).toVNode(create) ); diff --git a/src/components/directives/in-view/index.ts b/src/components/directives/in-view/index.ts index 3a0270f6aa..fe46b15bcc 100644 --- a/src/components/directives/in-view/index.ts +++ b/src/components/directives/in-view/index.ts @@ -55,7 +55,7 @@ function registerDirectiveValue( return; } - Array.concat([], value).forEach((dirOpts: Exclude) => { + Array.toArray(value).forEach((dirOpts: Exclude) => { let watchOpts: WatchOptions, handler: WatchHandler; @@ -90,7 +90,7 @@ function unregisterDirectiveValue(el: Element, value: Nullable) return; } - Array.concat([], value).forEach((opts: Exclude) => { + Array.toArray(value).forEach((opts: Exclude) => { IntersectionWatcher.unwatch(el, Object.isFunction(opts) ? opts : opts.handler); }); } diff --git a/src/components/directives/on-resize/index.ts b/src/components/directives/on-resize/index.ts index e0d1f124db..cf71b06ffc 100644 --- a/src/components/directives/on-resize/index.ts +++ b/src/components/directives/on-resize/index.ts @@ -51,7 +51,7 @@ function registerDirectiveValue(el: Element, value: CanUndef): v return; } - Array.concat([], value).forEach((dirOpts: Exclude) => { + Array.toArray(value).forEach((dirOpts: Exclude) => { let watchOpts: WatchOptions, handler: WatchHandler; @@ -74,7 +74,7 @@ function unregisterDirectiveValue(el: Element, value: Nullable) return; } - Array.concat([], value).forEach((opts: Exclude) => { + Array.toArray(value).forEach((opts: Exclude) => { ResizeWatcher.unwatch(el, Object.isFunction(opts) ? opts : opts.handler); }); } diff --git a/src/components/form/b-input/validators/index.ts b/src/components/form/b-input/validators/index.ts index 1a4571b33b..c71a31f771 100644 --- a/src/components/form/b-input/validators/index.ts +++ b/src/components/form/b-input/validators/index.ts @@ -61,6 +61,8 @@ export default >{ const numStyleRgxp = new RegExp(`[${Array.concat([], styleSeparator).join('')}]`, 'g'), sepStyleRgxp = new RegExp(`[${Array.concat([], separator).join('')}]`); + numStyleRgxp = new RegExp(`[${Array.toArray(styleSeparator).join('')}]`, 'g'), + sepStyleRgxp = new RegExp(`[${Array.toArray(separator).join('')}]`); const value = String((await this.formValue) ?? '') .replace(numStyleRgxp, '') diff --git a/src/components/form/b-select/b-select.ss b/src/components/form/b-select/b-select.ss index f46c6d4f1b..a79c52938b 100644 --- a/src/components/form/b-select/b-select.ss +++ b/src/components/form/b-select/b-select.ss @@ -36,7 +36,7 @@ :key = getItemKey(el, i) | :-id = values.getIndex(el.value) | - :class = Array.concat([], el.classes, provide.elementClasses({ + :class = Array.toArray(el.classes, provide.elementClasses({ item: { id: values.getIndex(el.value), selected: isSelected(el.value), diff --git a/src/components/form/b-select/b-select.ts b/src/components/form/b-select/b-select.ts index 505304244a..85399a1f8b 100644 --- a/src/components/form/b-select/b-select.ts +++ b/src/components/form/b-select/b-select.ts @@ -149,7 +149,7 @@ class bSelect extends iSelectProps implements iOpenToggle, iActiveItems { const val = this.field.get('defaultProp'); if (this.multiple) { - return new Set(Object.isIterable(val) ? val : Array.concat([], val)); + return new Set(Object.isIterable(val) ? val : Array.toArray(val)); } return val; @@ -306,7 +306,7 @@ class bSelect extends iSelectProps implements iOpenToggle, iActiveItems { } SyncPromise.resolve(this.activeElement).then((els) => { - Array.concat([], els).forEach((el) => { + Array.toArray(els).forEach((el) => { h.setSelectedMod.call(this, el, true); }); }).catch(stderr); @@ -331,7 +331,7 @@ class bSelect extends iSelectProps implements iOpenToggle, iActiveItems { } SyncPromise.resolve(previousActiveElement).then((els) => { - Array.concat([], els).forEach((el) => { + Array.toArray(els).forEach((el) => { const id = el.getAttribute('data-id'), item = this.values.getItem(id ?? -1); diff --git a/src/components/form/b-select/test/helpers.ts b/src/components/form/b-select/test/helpers.ts index df14cd73ca..b5c2713273 100644 --- a/src/components/form/b-select/test/helpers.ts +++ b/src/components/form/b-select/test/helpers.ts @@ -84,7 +84,7 @@ export async function selectValue(page: Page, target: JSHandle, value: } else { await input.focus(); - const promises = Array.concat([], value) + const promises = Array.toArray(value) .map((hasText) => page.locator(createSelector('item')).filter({hasText}).click()); await Promise.all(promises); diff --git a/src/components/friends/async-render/iterate.ts b/src/components/friends/async-render/iterate.ts index 18ee3112a1..409aabfbca 100644 --- a/src/components/friends/async-render/iterate.ts +++ b/src/components/friends/async-render/iterate.ts @@ -332,12 +332,11 @@ export function iterate( return lastTask(); function task() { - const - renderedVNodes: Node[] = []; + const renderedVNodes: Node[] = []; ctx.vdom.withRenderContext(() => { const vnodes = valuesToRender.flatMap((el) => { - const rawVNodes = Array.concat([], toVNode(el, iterI)); + const rawVNodes = Array.toArray(toVNode(el, iterI)); return rawVNodes.flatMap((vnode) => { if (Object.isSymbol(vnode.type) && Object.isArray(vnode.children)) { diff --git a/src/components/friends/daemons/helpers.ts b/src/components/friends/daemons/helpers.ts index 9587f1baa6..bdb6835753 100644 --- a/src/components/friends/daemons/helpers.ts +++ b/src/components/friends/daemons/helpers.ts @@ -40,7 +40,7 @@ export function mergeHooks(baseDaemon: Daemon, parentDaemon: Daemon): Hook[] { } return Array.from( - new Set(Array.concat([], parentHook, baseHook)) + new Set(Array.toArray(parentHook, baseHook)) ); } @@ -60,6 +60,6 @@ export function mergeWatchers(baseDaemon: Daemon, parentDaemon: Daemon): DaemonW } return Array.from( - new Set(Array.concat([], parentWatch, baseWatch)) + new Set(Array.toArray(parentWatch, baseWatch)) ); } diff --git a/src/components/friends/data-provider/request.ts b/src/components/friends/data-provider/request.ts index 0c351893d3..6242e2dff2 100644 --- a/src/components/friends/data-provider/request.ts +++ b/src/components/friends/data-provider/request.ts @@ -277,7 +277,7 @@ export function getDefaultRequestParams( const {ctx} = this; const [customData, customOpts] = Object.cast>( - Array.concat([], ctx.request?.[method]) + Array.toArray(ctx.request?.[method]) ); const diff --git a/src/components/friends/provide/classes.ts b/src/components/friends/provide/classes.ts index 754b026959..360527803d 100644 --- a/src/components/friends/provide/classes.ts +++ b/src/components/friends/provide/classes.ts @@ -106,7 +106,7 @@ export function classes( }); } - map[innerEl.dasherize()] = fullElementName.apply(this, Object.cast(Array.concat([componentName], outerEl))); + map[innerEl.dasherize()] = fullElementName.apply(this, Array.toArray(componentName, outerEl)); }); return map; diff --git a/src/components/friends/state/helpers.ts b/src/components/friends/state/helpers.ts index 95bdeb1fdb..15a6abfd0c 100644 --- a/src/components/friends/state/helpers.ts +++ b/src/components/friends/state/helpers.ts @@ -44,8 +44,7 @@ export function set(this: Friend, data: Nullable): Array { @@ -176,10 +176,10 @@ function createVNode( slots[key] = Object.isFunction(slot) ? function slotWrapper(this: unknown) { // eslint-disable-next-line prefer-rest-params - return Array.concat([], slot.apply(this, arguments)).map(Object.cast(factory)); + return Array.toArray(slot.apply(this, arguments)).map(Object.cast(factory)); } : - () => Array.concat([], slot).map(factory); + () => Array.toArray(slot).map(factory); }); resolvedChildren = slots; diff --git a/src/components/super/i-block/modules/listeners/index.ts b/src/components/super/i-block/modules/listeners/index.ts index fd67724891..c3c6b4bdaf 100644 --- a/src/components/super/i-block/modules/listeners/index.ts +++ b/src/components/super/i-block/modules/listeners/index.ts @@ -82,8 +82,7 @@ export function initGlobalListeners(component: iBlock, resetListener?: boolean): $e.on('reset.silence', waitNextTickForReset(async () => { if (needRouterSync || globalName != null) { - const tasks = Array.concat( - [], + const tasks = Array.toArray( needRouterSync ? $s.resetRouter() : null, globalName != null ? $s.resetStorage() : null ); @@ -122,7 +121,7 @@ export function initRemoteWatchers(component: iBlock): void { } Object.entries(watchProp).forEach(([method, watchers]) => { - Array.concat([], watchers).forEach((watcher) => { + Array.toArray( watchers).forEach((watcher) => { if (Object.isString(watcher)) { const path = normalizePath(watcher), @@ -140,7 +139,7 @@ export function initRemoteWatchers(component: iBlock): void { wList.push({ ...watcher, - args: Array.concat([], watcher.args), + args: Array.toArray(watcher.args), method, handler: method }); diff --git a/src/components/super/i-input-text/mask/handlers/input.ts b/src/components/super/i-input-text/mask/handlers/input.ts index 08be419fe7..a3958e9bfc 100644 --- a/src/components/super/i-input-text/mask/handlers/input.ts +++ b/src/components/super/i-input-text/mask/handlers/input.ts @@ -78,7 +78,7 @@ export function onKeyPress(this: Mask, e: KeyboardEvent): void { const additionalPlaceholder = [...fittedMask.placeholder.letters()].slice(boundedTextChunks.length), - fittedTextChunks = Array.concat([], boundedTextChunks, ...additionalPlaceholder); + fittedTextChunks = Array.toArray(boundedTextChunks, ...additionalPlaceholder); let symbolsInSelection = selectionRange + 1, diff --git a/src/components/traits/i-active-items/i-active-items.ts b/src/components/traits/i-active-items/i-active-items.ts index 502ba1c517..2e0ef40509 100644 --- a/src/components/traits/i-active-items/i-active-items.ts +++ b/src/components/traits/i-active-items/i-active-items.ts @@ -105,7 +105,7 @@ export default abstract class iActiveItems extends iItems { return ctx.activeStore; } - return new Set(Array.concat([], ctx.activeStore)); + return new Set(Array.toArray(ctx.activeStore)); } return ctx.activeStore; @@ -115,7 +115,7 @@ export default abstract class iActiveItems extends iItems { newVal; if (ctx.multiple) { - newVal = new Set(Object.isIterable(val) && !Object.isString(val) ? val : Array.concat([], val)); + newVal = new Set(Object.isIterable(val) && !Object.isString(val) ? val : Array.toArray(val)); if (Object.fastCompare(newVal, ctx.activeStore)) { return ctx.activeStore; diff --git a/src/components/traits/i-control-list/i-control-list.ts b/src/components/traits/i-control-list/i-control-list.ts index 53e5b966b9..8e72d56618 100644 --- a/src/components/traits/i-control-list/i-control-list.ts +++ b/src/components/traits/i-control-list/i-control-list.ts @@ -63,8 +63,7 @@ export default abstract class iControlList { return action.call(component); } - const - fullArgs = Array.concat([], action.defArgs ? args : null, action.args); + const fullArgs = Array.toArray(action.defArgs ? args : null, action.args); const {handler, argsMap} = action, diff --git a/src/core/async/index.ts b/src/core/async/index.ts index a1e84f9f0f..2933ceab1a 100644 --- a/src/core/async/index.ts +++ b/src/core/async/index.ts @@ -238,7 +238,7 @@ export default class Async> extends Super { return null; } - const clearHandlers = Array.concat([], p.onClear); + const clearHandlers = Array.toArray(p.onClear); p.onClear = clearHandlers; function dragStartClear(this: unknown, ...args: unknown[]): void { diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index e5d26c179a..ef889711cc 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -115,7 +115,7 @@ export function paramsFactory( if (p.watch != null) { watchers ??= {}; - Array.concat([], p.watch).forEach((watcher) => { + (Object.isArray(p.watch) ? p.watch : [p.watch]).forEach((watcher) => { if (Object.isPlainObject(watcher)) { const path = String(watcher.path ?? watcher.field); watchers[path] = wrapOpts({...p.watchParams, ...watcher, path}); @@ -129,7 +129,7 @@ export function paramsFactory( if (p.hook != null) { hooks ??= {}; - Array.concat([], p.hook).forEach((hook) => { + (Object.isArray(p.hook) ? p.hook : [p.hook]).forEach((hook) => { if (Object.isSimpleObject(hook)) { const hookName = Object.keys(hook)[0], @@ -212,7 +212,7 @@ export function paramsFactory( } if (p.watch != null) { - Array.concat([], p.watch).forEach((watcher) => { + (Object.isArray(p.watch) ? p.watch : [p.watch]).forEach((watcher) => { watchers ??= new Map(); if (Object.isPlainObject(watcher)) { diff --git a/src/core/component/directives/render/index.ts b/src/core/component/directives/render/index.ts index ed27e015d4..7ab102bafe 100644 --- a/src/core/component/directives/render/index.ts +++ b/src/core/component/directives/render/index.ts @@ -41,8 +41,7 @@ ComponentEngine.directive('render', { } if (Object.isString(vnode.type)) { - const - children = Array.concat([], newVNode); + const children = Array.toArray(newVNode); if (SSR) { if (isTemplate) { diff --git a/src/core/component/engines/vue3/render.ts b/src/core/component/engines/vue3/render.ts index d8bb8740c1..26f567870d 100644 --- a/src/core/component/engines/vue3/render.ts +++ b/src/core/component/engines/vue3/render.ts @@ -241,7 +241,7 @@ export function render(vnode: CanArray, parent?: ComponentInterface, grou }); gc.add(function* destructor() { - const vnodes = Object.isArray(vnode) ? vnode : [vnode]; + const vnodes = Array.toArray(vnode); for (const vnode of vnodes) { destroy(vnode); diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index 7c22039867..7035d6a2d8 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -148,13 +148,12 @@ export function implementEventEmitterAPI(component: object): void { emitter = Object.cast(opts.rawEmitter ? reversedEmitter : wrappedReversedEmitter); } - Array.concat([], event).forEach((event) => { + Array.toArray(event).forEach((event) => { if (method === 'off' && cb == null) { emitter.removeAllListeners(event); } else { - const - link = emitter[method](Object.cast(event), Object.cast(cb)); + const link = emitter[method](Object.cast(event), Object.cast(cb)); if (isOnLike) { links.push(Object.cast(opts.rawEmitter ? cb : link)); diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index cddf8fbdca..65651c47fa 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -257,7 +257,7 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor watcherListeners.push({ ...watcher, method: methodName, - args: Array.concat([], watcher.args), + args: Array.toArray(watcher.args), handler: Object.cast(method.fn) }); }); diff --git a/src/core/component/reflect/mod.ts b/src/core/component/reflect/mod.ts index 6942d3b418..24df579830 100644 --- a/src/core/component/reflect/mod.ts +++ b/src/core/component/reflect/mod.ts @@ -51,7 +51,7 @@ export function getComponentMods(component: ComponentConstructorInfo): ModsDecl if (Object.isDictionary(modsFromDS)) { Object.entries(modsFromDS).forEach(([name, dsModDecl]) => { const modDecl = modsFromConstructor[name]; - modsFromConstructor[name] = Object.cast(Array.concat([], modDecl, dsModDecl)); + modsFromConstructor[name] = Object.cast(Array.toArray(modDecl, dsModDecl)); }); } diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 3188fef28b..36bd3e8715 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -173,7 +173,7 @@ export function wrapCreateBlock(original: T): T { vnode.children = functionalVNode.children; vnode.dynamicChildren = functionalVNode.dynamicChildren; - vnode.dirs = Array.concat([], vnode.dirs, functionalVNode.dirs); + vnode.dirs = Array.toArray(vnode.dirs, functionalVNode.dirs); vnode.dirs.push({ dir: Object.cast(r.resolveDirective.call(virtualCtx, 'hook')), From 8c2bbd5ab810bc69125291d727b6500062a34ee9 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 15:50:58 +0300 Subject: [PATCH 071/334] chore: use Array.toArray --- src/components/form/b-input/validators/index.ts | 2 -- src/components/super/i-block/modules/listeners/index.ts | 3 +-- src/core/component/directives/render/index.ts | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/form/b-input/validators/index.ts b/src/components/form/b-input/validators/index.ts index c71a31f771..295d0ce195 100644 --- a/src/components/form/b-input/validators/index.ts +++ b/src/components/form/b-input/validators/index.ts @@ -59,8 +59,6 @@ export default >{ showMessage = true }: NumberValidatorParams): Promise> { const - numStyleRgxp = new RegExp(`[${Array.concat([], styleSeparator).join('')}]`, 'g'), - sepStyleRgxp = new RegExp(`[${Array.concat([], separator).join('')}]`); numStyleRgxp = new RegExp(`[${Array.toArray(styleSeparator).join('')}]`, 'g'), sepStyleRgxp = new RegExp(`[${Array.toArray(separator).join('')}]`); diff --git a/src/components/super/i-block/modules/listeners/index.ts b/src/components/super/i-block/modules/listeners/index.ts index c3c6b4bdaf..b0675d047b 100644 --- a/src/components/super/i-block/modules/listeners/index.ts +++ b/src/components/super/i-block/modules/listeners/index.ts @@ -68,8 +68,7 @@ export function initGlobalListeners(component: iBlock, resetListener?: boolean): ctx.componentStatus = 'loading'; if (needRouterSync || globalName != null) { - const tasks = Array.concat( - [], + const tasks = Array.toArray( needRouterSync ? $s.resetRouter() : null, globalName != null ? $s.resetStorage() : null ); diff --git a/src/core/component/directives/render/index.ts b/src/core/component/directives/render/index.ts index 7ab102bafe..66e720f211 100644 --- a/src/core/component/directives/render/index.ts +++ b/src/core/component/directives/render/index.ts @@ -96,7 +96,7 @@ ComponentEngine.directive('render', { } async function getSSRInnerHTML(content: CanArray>) { - let normalizedContent = Array.concat([], content); + let normalizedContent = Array.toArray(content); while (normalizedContent.some(Object.isPromise)) { normalizedContent = (await Promise.all(normalizedContent)).flat(); From f0ec0bbd5d79958dc492b9bd18160954ccad2861 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 16:01:22 +0300 Subject: [PATCH 072/334] chore: fixed eslint --- src/components/super/i-block/modules/listeners/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/modules/listeners/index.ts b/src/components/super/i-block/modules/listeners/index.ts index b0675d047b..9b325b7103 100644 --- a/src/components/super/i-block/modules/listeners/index.ts +++ b/src/components/super/i-block/modules/listeners/index.ts @@ -120,7 +120,7 @@ export function initRemoteWatchers(component: iBlock): void { } Object.entries(watchProp).forEach(([method, watchers]) => { - Array.toArray( watchers).forEach((watcher) => { + Array.toArray(watchers).forEach((watcher) => { if (Object.isString(watcher)) { const path = normalizePath(watcher), From eead0a25d0c1b9da3c452c89afc92e5e82937c47 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 16:11:54 +0300 Subject: [PATCH 073/334] chore: fixed tests --- src/components/super/i-block/test/unit/teleports.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/super/i-block/test/unit/teleports.ts b/src/components/super/i-block/test/unit/teleports.ts index 13c30880cf..daa2e2c84a 100644 --- a/src/components/super/i-block/test/unit/teleports.ts +++ b/src/components/super/i-block/test/unit/teleports.ts @@ -45,7 +45,7 @@ test.describe(' using the root teleport', () => { const attrs = await target.evaluate((ctx) => ctx.unsafe.$refs.component.$el!.className); - test.expect(attrs).toBe('i-block-helper u3b379f9a91182 b-bottom-slide b-bottom-slide_opened_false b-bottom-slide_stick_true b-bottom-slide_events_false b-bottom-slide_height-mode_full b-bottom-slide_visible_false b-bottom-slide_theme_light b-bottom-slide_hidden_true'); + test.expect(attrs).toBe('i-block-helper u1e705d34abc46a b-bottom-slide b-bottom-slide_opened_false b-bottom-slide_stick_true b-bottom-slide_events_false b-bottom-slide_height-mode_full b-bottom-slide_visible_false b-bottom-slide_theme_light b-bottom-slide_hidden_true'); }); }); @@ -76,7 +76,7 @@ test.describe(' using the root teleport', () => { const attrs = await target.evaluate((ctx) => ctx.unsafe.$el!.className); - test.expect(attrs).toBe('i-block-helper ue3771dae8cf71 b-bottom-slide b-bottom-slide_opened_false b-bottom-slide_hidden_true b-bottom-slide_stick_true b-bottom-slide_events_false b-bottom-slide_height-mode_full b-bottom-slide_theme_light b-bottom-slide_visible_false'); + test.expect(attrs).toBe('i-block-helper u368b46b330c1c b-bottom-slide b-bottom-slide_opened_false b-bottom-slide_hidden_true b-bottom-slide_stick_true b-bottom-slide_events_false b-bottom-slide_height-mode_full b-bottom-slide_theme_light b-bottom-slide_visible_false'); }); }); }); From b041f4fce6539122a76c3b93ff3d80808ed9a726 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 16:19:27 +0300 Subject: [PATCH 074/334] chore: optimization --- src/components/friends/block/block.ts | 3 +-- src/components/friends/vdom/traverse.ts | 17 +++++------------ src/components/super/i-block/i-block.ss | 2 +- src/components/super/i-block/interface.ts | 6 ++++++ src/components/super/i-data/data.ts | 9 ++++++++- src/components/traits/i-visible/i-visible.ts | 3 +-- 6 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/components/friends/block/block.ts b/src/components/friends/block/block.ts index 75323eb6c0..2c511bc72a 100644 --- a/src/components/friends/block/block.ts +++ b/src/components/friends/block/block.ts @@ -151,8 +151,7 @@ export function setMod(this: Block, name: string, value: unknown, reason: ModEve ctx.mods[name] = normalizedValue; if (!ctx.isFunctional) { - const - reactiveModsStore = ctx.field.get('reactiveModsStore'); + const {reactiveModsStore} = ctx.field.getFieldsStore(); if (reactiveModsStore != null && name in reactiveModsStore && reactiveModsStore[name] !== normalizedValue) { delete Object.getPrototypeOf(reactiveModsStore)[name]; diff --git a/src/components/friends/vdom/traverse.ts b/src/components/friends/vdom/traverse.ts index e042f2f724..c0014b72aa 100644 --- a/src/components/friends/vdom/traverse.ts +++ b/src/components/friends/vdom/traverse.ts @@ -79,32 +79,25 @@ export function findElement( return search(where); function search(vnode: VNode) { - const - props = vnode.props ?? {}; + const props = vnode.props ?? {}; if (props.class != null) { - const - classes = normalizeClass(props.class).split(' '); + const classes = normalizeClass(props.class).split(' '); if (classes.includes(selector)) { return vnode; } } - const - {children} = vnode; + const {children} = vnode; if (Object.isArray(children)) { - for (let i = 0; i < children.length; i++) { - const - el = children[i]; - + for (const el of children) { if (Object.isPrimitive(el) || Object.isArray(el)) { continue; } - const - res = search(Object.cast(el)); + const res = search(Object.cast(el)); if (res != null) { return res; diff --git a/src/components/super/i-block/i-block.ss b/src/components/super/i-block/i-block.ss index 5cc548798c..0c7fee113a 100644 --- a/src/components/super/i-block/i-block.ss +++ b/src/components/super/i-block/i-block.ss @@ -96,7 +96,7 @@ : & buble = require('buble'), - paths = Array.concat([], path), + paths = Array.toArray(path), wait = opts.wait . diff --git a/src/components/super/i-block/interface.ts b/src/components/super/i-block/interface.ts index 3b78534de4..3b2bdd717b 100644 --- a/src/components/super/i-block/interface.ts +++ b/src/components/super/i-block/interface.ts @@ -106,6 +106,12 @@ export interface UnsafeIBlock extends UnsafeCompone // @ts-ignore (access) ifOnceStore: CTX['ifOnceStore']; + // @ts-ignore (access) + reactiveModsStore: CTX['reactiveModsStore']; + + // @ts-ignore (access) + rootAttrsStore: CTX['rootAttrsStore']; + // @ts-ignore (access) syncRouterState: CTX['syncRouterState']; diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index 4b7e5c996f..73588af38c 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -88,7 +88,14 @@ abstract class iDataData extends iBlock implements iDataProvider { * These functions step by step transform the original provider data before storing it in `db`. * {@link iDataProvider.dbConverter} */ - @system((o) => o.sync.link('dbConverter', (val) => Array.concat([], Object.isIterable(val) ? [...val] : val))) + @system((o) => o.sync.link('dbConverter', (val) => { + if (val == null) { + return []; + } + + return Object.isIterable(val) ? [...val] : [val]; + })) + dbConverters!: ComponentConverter[]; /** diff --git a/src/components/traits/i-visible/i-visible.ts b/src/components/traits/i-visible/i-visible.ts index f2fa368f2c..5da1c68474 100644 --- a/src/components/traits/i-visible/i-visible.ts +++ b/src/components/traits/i-visible/i-visible.ts @@ -45,8 +45,7 @@ export default abstract class iVisible { localEmitter: $e } = component.unsafe; - component.sync - .mod('hidden', 'r.isOnline', (v) => component.hideIfOffline && v === false); + component.sync.mod('hidden', 'r.isOnline', (v) => component.hideIfOffline && v === false); $e.on('block.mod.*.hidden.*', (e: ModEvent) => { if (e.type === 'remove' && e.reason !== 'removeMod') { From ba8699c63021bb46898e9940f1a1c6916adb5b05 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 17:22:34 +0300 Subject: [PATCH 075/334] chore: fixed tree-shake --- src/components/super/i-block/friends.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/super/i-block/friends.ts b/src/components/super/i-block/friends.ts index 30e7778739..a94c7952db 100644 --- a/src/components/super/i-block/friends.ts +++ b/src/components/super/i-block/friends.ts @@ -59,7 +59,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get field(): Field { - const Field = require('components/friends/field').default; + const Field = require('components/friends/field/class').default; return new Field(Object.cast(this)); } @@ -80,7 +80,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get sync(): Sync { - const Sync = require('components/friends/sync').default; + const Sync = require('components/friends/sync/class').default; return new Sync(Object.cast(this)); } @@ -95,7 +95,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get asyncRender(): AsyncRender { - const AsyncRender = require('components/friends/async-render').default; + const AsyncRender = require('components/friends/async-render/class').default; return new AsyncRender(Object.cast(this)); } @@ -104,7 +104,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get vdom(): VDOM { - const VDOM = require('components/friends/vdom').default; + const VDOM = require('components/friends/vdom/class').default; return new VDOM(Object.cast(this)); } @@ -113,7 +113,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get infoRender(): InfoRender { - const InfoRender = require('components/friends/info-render').default; + const InfoRender = require('components/friends/info-render/class').default; return new InfoRender(Object.cast(this)); } @@ -150,7 +150,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get dom(): DOM { - const DOM = require('components/friends/dom').default; + const DOM = require('components/friends/dom/class').default; return new DOM(Object.cast(this)); } @@ -159,7 +159,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get storage(): Storage { - const Storage = require('components/friends/storage').default; + const Storage = require('components/friends/storage/class').default; return new Storage(Object.cast(this)); } @@ -168,7 +168,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get state(): State { - const State = require('components/friends/state').default; + const State = require('components/friends/state/class').default; return new State(Object.cast(this)); } @@ -177,7 +177,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get moduleLoader(): ModuleLoader { - const ModuleLoader = require('components/friends/module-loader').default; + const ModuleLoader = require('components/friends/module-loader/class').default; return new ModuleLoader(Object.cast(this)); } @@ -186,7 +186,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) protected get daemons(): Daemons { - const Daemons = require('components/friends/daemons').default; + const Daemons = require('components/friends/daemons/class').default; return new Daemons(Object.cast(this)); } From b4f0bcbd784f1687d4395e87c5dbca9f931268fe Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 17:28:29 +0300 Subject: [PATCH 076/334] fix: fixed import --- src/components/super/i-block/friends.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/friends.ts b/src/components/super/i-block/friends.ts index a94c7952db..6bb8e0b5ae 100644 --- a/src/components/super/i-block/friends.ts +++ b/src/components/super/i-block/friends.ts @@ -113,7 +113,7 @@ export default abstract class iBlockFriends extends iBlockProps { */ @computed({cache: 'forever'}) get infoRender(): InfoRender { - const InfoRender = require('components/friends/info-render/class').default; + const InfoRender = require('components/friends/info-render').default; return new InfoRender(Object.cast(this)); } From b61cfa3bf4f01cfcfdbea2811797a86d9b6b13d2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 23 Aug 2024 17:52:23 +0300 Subject: [PATCH 077/334] chore: fixed eslint --- src/components/friends/block/block.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/friends/block/block.ts b/src/components/friends/block/block.ts index 2c511bc72a..733883f1d7 100644 --- a/src/components/friends/block/block.ts +++ b/src/components/friends/block/block.ts @@ -153,7 +153,7 @@ export function setMod(this: Block, name: string, value: unknown, reason: ModEve if (!ctx.isFunctional) { const {reactiveModsStore} = ctx.field.getFieldsStore(); - if (reactiveModsStore != null && name in reactiveModsStore && reactiveModsStore[name] !== normalizedValue) { + if (name in reactiveModsStore && reactiveModsStore[name] !== normalizedValue) { delete Object.getPrototypeOf(reactiveModsStore)[name]; ctx.field.set(`reactiveModsStore.${name}`, normalizedValue); } From 8c0c526e5089913e3e0bddad4b8970536f9780fb Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 26 Aug 2024 14:58:57 +0300 Subject: [PATCH 078/334] refactor: review the module --- src/core/component/functional/README.md | 36 ++++++----- .../{context.ts => context/create.ts} | 19 +++--- .../component/functional/context/index.ts | 10 +++ .../{helpers.ts => context/inherit.ts} | 59 ++---------------- src/core/component/functional/index.ts | 4 +- .../component/functional/life-cycle/index.ts | 10 +++ .../component/functional/life-cycle/init.ts | 60 ++++++++++++++++++ .../component/functional/life-cycle/v-hook.ts | 61 +++++++++++++++++++ 8 files changed, 177 insertions(+), 82 deletions(-) rename src/core/component/functional/{context.ts => context/create.ts} (92%) create mode 100644 src/core/component/functional/context/index.ts rename src/core/component/functional/{helpers.ts => context/inherit.ts} (58%) create mode 100644 src/core/component/functional/life-cycle/index.ts create mode 100644 src/core/component/functional/life-cycle/init.ts create mode 100644 src/core/component/functional/life-cycle/v-hook.ts diff --git a/src/core/component/functional/README.md b/src/core/component/functional/README.md index 79bdc1769b..3c7353458a 100644 --- a/src/core/component/functional/README.md +++ b/src/core/component/functional/README.md @@ -3,7 +3,7 @@ This module provides an API for creating functional components. It should be noted that V4Fire has its own implementation of cross-platform functional components available as well. -## What differences between regular and functional components? +## What Are the Differences Between Regular and Functional Components? The main difference between regular and functional components is how they handle template updates. In regular components, the template is automatically updated whenever the component's state changes. @@ -15,25 +15,25 @@ Additionally, functional components provide the option to disable watching for c This can be done by using the `functional: false` option when applying property or method decorators to the component. By disabling watching for specific properties or events, the performance of functional components can be optimized. -### Why are functional components called like that? +### Why are Functional Components Called Like That? Functional components are called like that because they are essentially represented as ordinary functions. These functions accept input parameters, such as component props, and have their own context or state. However, what sets them apart from regular components is that they are executed only once during rendering. -## Why are functional components needed? +## Why are Functional Components Necessary? They are faster to initialize and render compared to regular components. As a result, if there are many functional components on a page, the performance gain can be quite noticeable. -## When should functional components be used? +## When Should Functional Components Be Used? When a component template remains unchanged regardless of the component's state. Mind, you can still use component modifiers to alter the appearance of the template. For instance, you can hide or display specific portions of the template. This is possible because modifiers are always assigned as regular CSS classes to the root component element. -## When should you not use functional components? +## When Should You Not Use Functional Components? There is a fundamental rule to keep in mind: if your component's markup is dependent on the state, such as retrieving and displaying data from the network, then a functional component may not be the best option for you. @@ -43,7 +43,7 @@ The issue arises when any non-functional parent of such a component undergoes a causing all child functional components to be recreated. Consequently, any ongoing animations will be reset. -## How is a functional component update when its non-functional parent component updates? +## How is a Functional Component Updated When Its Non-Functional Parent Component Updates? In scenarios where a component's non-functional parent undergoes an update, a common strategy is employed: the component is first destroyed and then recreated. @@ -51,9 +51,9 @@ During the creation of the new component, it typically retains the state from th In simple terms, if any properties were modified in the previous component, these modifications will carry over to the new component. -Nonetheless, we can enhance this behavior by utilizing the special options available in decorators. +Nonetheless, we can enhance this behavior by using the special options available in decorators. -## How to create a functional component? +## Creating a Functional Component There are two ways to declare a functional component. @@ -118,7 +118,7 @@ There are two ways to declare a functional component. } ``` -2. If you pass the `functional` option as an object, you have the ability to specify under which output parameters a +2. If you pass the `functional` option as an object, you can specify under which output parameters a functional component should be used, and under which a regular component should be used. This type of component is commonly referred to as a "smart" component. It's worth noting that this option is inherited from the parent component to the child component, @@ -151,15 +151,15 @@ There are two ways to declare a functional component. < bLink v-func = false ``` -## How to update the template of a functional component? +## How to Update the Template of a Functional Component? Ideally, it is necessary to avoid such situations. After all, if you want to update the template of a component when its state changes, it means you need a regular component instead of a functional one. However, if you do need to update the template of a functional component, -there are several standard ways to accomplish this. +there are several standard ways to achieve this. -### Using modifiers +### Using Modifiers This approach allows you to manipulate the template by adding or removing CSS classes associated with the root node of the component. @@ -191,9 +191,9 @@ __b-link/b-link.ss__ - rootAttrs = {'@click': 'onClick'} ``` -### Using node references and the DOM API +### Using Node References and the DOM API -By utilizing this approach, you can delete or add attributes to a specific DOM node or even modify its structure. +By using this approach, you can delete or add attributes to a specific DOM node or even modify its structure. However, exercise extreme caution when adding new nodes. __b-link/b-link.ts__ @@ -221,7 +221,7 @@ __b-link/b-link.ss__ < a ref = link ``` -### Using the `v-update-on` directive +### Using the `v-update-on` Directive This directive, in conjunction with the modifiers API and the DOM API, provides you with greater flexibility in updating nodes. @@ -263,6 +263,12 @@ __b-link/b-link.ss__ += self.slot() ``` +## Classes + +### VHookLifeCycle + +A class for integrating lifecycle events of a functional component using the `v-hook` directive. + ## Functions ### createVirtualContext diff --git a/src/core/component/functional/context.ts b/src/core/component/functional/context/create.ts similarity index 92% rename from src/core/component/functional/context.ts rename to src/core/component/functional/context/create.ts index 75ff726559..eb5f6b2bb7 100644 --- a/src/core/component/functional/context.ts +++ b/src/core/component/functional/context/create.ts @@ -12,11 +12,11 @@ import { saveRawComponentContext } from 'core/component/context'; import { forkMeta, ComponentMeta } from 'core/component/meta'; import { initProps } from 'core/component/prop'; -import { initDynamicComponentLifeCycle } from 'core/component/functional/helpers'; - import type { ComponentInterface } from 'core/component/interface'; import type { VirtualContextOptions } from 'core/component/functional/interface'; +import { initDynamicComponentLifeCycle } from 'core/component/functional/life-cycle'; + /** * Creates a virtual context for the passed functional component * @@ -37,8 +37,7 @@ export function createVirtualContext( $props = {}, $attrs = {}; - const - handlers: Array<[string, boolean, Function]> = []; + const handlers: Array<[string, boolean, Function]> = []; if (props != null) { const @@ -54,19 +53,16 @@ export function createVirtualContext( }; Object.entries(props).forEach(([name, prop]) => { - const - normalizedName = name.camelize(false); + const normalizedName = name.camelize(false); if (normalizedName in meta.props) { $props[normalizedName] = prop; } else { if (isComponentEventHandler(name, prop)) { - let - event = name.slice('on'.length).camelize(false); + let event = name.slice('on'.length).camelize(false); - const - once = isOnceEvent.test(name); + const once = isOnceEvent.test(name); if (once) { event = event.replace(/Once$/, ''); @@ -80,8 +76,7 @@ export function createVirtualContext( }); } - let - $options: {directives: Dictionary; components: Dictionary}; + let $options: {directives: Dictionary; components: Dictionary}; if ('$options' in parent) { const { diff --git a/src/core/component/functional/context/index.ts b/src/core/component/functional/context/index.ts new file mode 100644 index 0000000000..2d016157c4 --- /dev/null +++ b/src/core/component/functional/context/index.ts @@ -0,0 +1,10 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +export * from 'core/component/functional/context/create'; +export * from 'core/component/functional/context/inherit'; diff --git a/src/core/component/functional/helpers.ts b/src/core/component/functional/context/inherit.ts similarity index 58% rename from src/core/component/functional/helpers.ts rename to src/core/component/functional/context/inherit.ts index a28dc9030d..aabe9883e3 100644 --- a/src/core/component/functional/helpers.ts +++ b/src/core/component/functional/context/inherit.ts @@ -6,59 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import * as init from 'core/component/init'; -import type { ComponentInterface, ComponentElement, ComponentDestructorOptions } from 'core/component/interface'; +import type { ComponentInterface, ComponentDestructorOptions } from 'core/component/interface'; /** - * Initializes the default dynamic lifecycle handlers for the given functional component. - * It also enables the component to emit lifecycle events such as `mounted` or `destroyed` hooks. - * - * @param component - */ -export function initDynamicComponentLifeCycle(component: ComponentInterface): ComponentInterface { - const {unsafe} = component; - unsafe.$on('[[COMPONENT_HOOK]]', hookHandler); - return component; - - function hookHandler(hook: string, node: ComponentElement) { - switch (hook) { - case 'mounted': - mount(); - break; - - case 'beforeUpdate': - break; - - case 'updated': { - inheritContext(unsafe, node.component); - init.createdState(component); - mount(); - break; - } - - case 'beforeDestroy': { - unsafe.$destroy(); - break; - } - - default: - init[`${hook}State`](unsafe); - } - - function mount() { - // @ts-ignore (unsafe) - unsafe.$el = node; - node.component = unsafe; - - // Performs a mount on the next tick to ensure that the component is rendered - // and all adjacent re-renders have collapsed - unsafe.$async.nextTick().then(() => init.mountedState(component)).catch(stderr); - } - } -} - -/** - * Overrides inheritable parameters for the given component based on the parent + * Overrides inheritable parameters for the given component based on those of the parent * * @param ctx * @param parentCtx @@ -76,8 +27,8 @@ export function inheritContext( } // Here, the functional component is recreated during re-rendering. - // Therefore, the destructor call should not recursively propagate to child components. - // Also, we should not unmount the vnodes created within the component. + // Therefore, the destructor call should not be recursively propagated to child components. + // Additionally, we should not unmount the vnodes created within the component. parentCtx.$destroy({recursive: false, shouldUnmountVNodes: false}); const @@ -137,7 +88,7 @@ export function inheritContext( if (field.merge === true) { let newVal = oldVal; - if (Object.isPlainObject(val) || Object.isPlainObject(oldVal)) { + if (Object.isDictionary(val) || Object.isDictionary(oldVal)) { // eslint-disable-next-line prefer-object-spread newVal = Object.assign({}, val, oldVal); diff --git a/src/core/component/functional/index.ts b/src/core/component/functional/index.ts index bac9e08a28..2fdb58d885 100644 --- a/src/core/component/functional/index.ts +++ b/src/core/component/functional/index.ts @@ -11,7 +11,9 @@ * @packageDocumentation */ -export * from 'core/component/functional/context'; +export { createVirtualContext } from 'core/component/functional/context'; +export { VHookLifeCycle } from 'core/component/functional/life-cycle'; + export * from 'core/component/functional/interface'; //#if runtime has dummyComponents diff --git a/src/core/component/functional/life-cycle/index.ts b/src/core/component/functional/life-cycle/index.ts new file mode 100644 index 0000000000..c38c7f5b56 --- /dev/null +++ b/src/core/component/functional/life-cycle/index.ts @@ -0,0 +1,10 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +export * from 'core/component/functional/life-cycle/v-hook'; +export * from 'core/component/functional/life-cycle/init'; diff --git a/src/core/component/functional/life-cycle/init.ts b/src/core/component/functional/life-cycle/init.ts new file mode 100644 index 0000000000..66d220115b --- /dev/null +++ b/src/core/component/functional/life-cycle/init.ts @@ -0,0 +1,60 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import * as init from 'core/component/init'; +import type { ComponentInterface, ComponentElement } from 'core/component/interface'; + +import { inheritContext } from 'core/component/functional/context/inherit'; + +/** + * Initializes the default dynamic lifecycle handlers for the given functional component. + * It also enables the component to emit lifecycle events such as mount and destroy hooks. + * + * @param component + */ +export function initDynamicComponentLifeCycle(component: ComponentInterface): ComponentInterface { + const {unsafe} = component; + unsafe.$on('[[COMPONENT_HOOK]]', hookHandler); + return component; + + function hookHandler(hook: string, node: ComponentElement) { + switch (hook) { + case 'mounted': + mount(); + break; + + case 'beforeUpdate': + break; + + case 'updated': { + inheritContext(unsafe, node.component); + init.createdState(component); + mount(); + break; + } + + case 'beforeDestroy': { + unsafe.$destroy(); + break; + } + + default: + init[`${hook}State`](unsafe); + } + + function mount() { + // @ts-ignore (unsafe) + unsafe.$el = node; + node.component = unsafe; + + // Performs a mount on the next tick to ensure that the component is rendered + // and all adjacent re-renders have collapsed + unsafe.$async.nextTick().then(() => init.mountedState(component)).catch(stderr); + } + } +} diff --git a/src/core/component/functional/life-cycle/v-hook.ts b/src/core/component/functional/life-cycle/v-hook.ts new file mode 100644 index 0000000000..96ffc91424 --- /dev/null +++ b/src/core/component/functional/life-cycle/v-hook.ts @@ -0,0 +1,61 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +/* eslint-disable @v4fire/require-jsdoc */ + +import { destroyedHooks } from 'core/component/const'; +import type { ComponentInterface } from 'core/component/interface'; + +/** + * A class for integrating lifecycle events of a functional component using the v-hook directive + */ +export class VHookLifeCycle { + protected ctx: ComponentInterface['unsafe']; + + constructor(ctx: ComponentInterface) { + this.ctx = Object.cast(ctx); + } + + created(n: Element): void { + this.ctx.$emit('[[COMPONENT_HOOK]]', 'created', n); + } + + beforeMount(n: Element): void { + this.ctx.$emit('[[COMPONENT_HOOK]]', 'beforeMount', n); + } + + mounted(n: Element): void { + this.ctx.$emit('[[COMPONENT_HOOK]]', 'mounted', n); + } + + beforeUpdate(n: Element): void { + this.ctx.$emit('[[COMPONENT_HOOK]]', 'beforeUpdate', n); + } + + updated(n: Element): void { + this.ctx.$emit('[[COMPONENT_HOOK]]', 'updated', n); + } + + beforeUnmount(n: Element): void { + // A component might have already been removed by explicitly calling $destroy + if (destroyedHooks[this.ctx.hook] != null) { + return; + } + + this.ctx.$emit('[[COMPONENT_HOOK]]', 'beforeDestroy', n); + } + + unmounted(n: Element): void { + // A component might have already been removed by explicitly calling $destroy + if (destroyedHooks[this.ctx.hook] != null) { + return; + } + + this.ctx.$emit('[[COMPONENT_HOOK]]', 'destroyed', n); + } +} From 5e1a39ad627e6f04c40b18bf9026b94f0c7793af Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 26 Aug 2024 15:36:01 +0300 Subject: [PATCH 079/334] chore: refactoring & optimization --- .../component/functional/context/create.ts | 23 +--- .../component/functional/context/helpers.ts | 53 ++++++++ .../component/render/helpers/normalizers.ts | 20 +-- src/core/component/render/helpers/props.ts | 15 ++- src/core/component/render/wrappers.ts | 117 ++++++------------ 5 files changed, 122 insertions(+), 106 deletions(-) create mode 100644 src/core/component/functional/context/helpers.ts diff --git a/src/core/component/functional/context/create.ts b/src/core/component/functional/context/create.ts index eb5f6b2bb7..1a67d35ab7 100644 --- a/src/core/component/functional/context/create.ts +++ b/src/core/component/functional/context/create.ts @@ -16,6 +16,7 @@ import type { ComponentInterface } from 'core/component/interface'; import type { VirtualContextOptions } from 'core/component/functional/interface'; import { initDynamicComponentLifeCycle } from 'core/component/functional/life-cycle'; +import { isComponentEventHandler, isOnceEvent } from 'core/component/functional/context/helpers'; /** * Creates a virtual context for the passed functional component @@ -28,7 +29,7 @@ import { initDynamicComponentLifeCycle } from 'core/component/functional/life-cy */ export function createVirtualContext( component: ComponentMeta, - {parent, props = {}, slots = {}}: VirtualContextOptions + {parent, props, slots}: VirtualContextOptions ): ComponentInterface { const meta = forkMeta(component); meta.params.functional = true; @@ -40,18 +41,6 @@ export function createVirtualContext( const handlers: Array<[string, boolean, Function]> = []; if (props != null) { - const - isOnceEvent = /.Once(.|$)/, - isDOMEvent = /.(?:Passive|Capture)(.|$)/; - - const isComponentEventHandler = (event: string, handler: unknown): handler is Function => { - if (!event.startsWith('on') || isDOMEvent.test(event) || !Object.isFunction(handler)) { - return false; - } - - return handler.name !== 'withModifiers' && handler.name !== 'withKeys'; - }; - Object.entries(props).forEach(([name, prop]) => { const normalizedName = name.camelize(false); @@ -62,13 +51,13 @@ export function createVirtualContext( if (isComponentEventHandler(name, prop)) { let event = name.slice('on'.length).camelize(false); - const once = isOnceEvent.test(name); + const isOnce = isOnceEvent.test(name); - if (once) { - event = event.replace(/Once$/, ''); + if (isOnce) { + event = isOnceEvent.replace(event); } - handlers.push([event, once, prop]); + handlers.push([event, isOnce, prop]); } $attrs[name] = prop; diff --git a/src/core/component/functional/context/helpers.ts b/src/core/component/functional/context/helpers.ts new file mode 100644 index 0000000000..30330a4651 --- /dev/null +++ b/src/core/component/functional/context/helpers.ts @@ -0,0 +1,53 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +const + minOnceEventNameLength = 'Once'.length + 'on'.length + 1, + onceEventRgxp = /\bOnce\b/; + +export const isOnceEvent = { + test(event: string): boolean { + if (event.length < minOnceEventNameLength || event.startsWith('on:')) { + return false; + } + + return event.endsWith('Once') || onceEventRgxp.test(event); + }, + + replace(event: string): string { + return event.replace('Once', ''); + } +}; + +export const + minDOMEventNameLength = Math.max('Passive'.length, 'Capture'.length) + 'on'.length + 1, + domEventRgxp = /\b(Passive|Capture)\b/; + +export const isDOMEvent = { + test(event: string): boolean { + if (event.length < minDOMEventNameLength || event.startsWith('on:')) { + return false; + } + + return event.endsWith('Passive') || event.endsWith('Capture') || domEventRgxp.test(event); + } +}; + +/** + * Checks if a given handler is a component event handler + * + * @param event - the event name to check + * @param handler - the handler to check + */ +export function isComponentEventHandler(event: string, handler: unknown): handler is Function { + if (!event.startsWith('on') || isDOMEvent.test(event) || !Object.isFunction(handler)) { + return false; + } + + return handler.name !== 'withModifiers' && handler.name !== 'withKeys'; +} diff --git a/src/core/component/render/helpers/normalizers.ts b/src/core/component/render/helpers/normalizers.ts index fa4efdf79c..b344208196 100644 --- a/src/core/component/render/helpers/normalizers.ts +++ b/src/core/component/render/helpers/normalizers.ts @@ -21,8 +21,7 @@ export function normalizeClass(classes: CanArray): string { } else if (Object.isArray(classes)) { classes.forEach((className) => { - const - normalizedClass = normalizeClass(className); + const normalizedClass = normalizeClass(className); if (normalizedClass !== '') { classesStr += `${normalizedClass} `; @@ -87,8 +86,7 @@ export function parseStringStyle(style: string): Dictionary { singleStyle = singleStyle.trim(); if (singleStyle !== '') { - const - chunks = singleStyle.split(propertyDelimiterRgxp); + const chunks = singleStyle.split(propertyDelimiterRgxp, 2); if (chunks.length > 1) { styles[chunks[0].trim()] = chunks[1].trim(); @@ -121,9 +119,9 @@ export function normalizeComponentAttrs( return null; } - const - dynamicPropsPatches = new Map(), - normalizedAttrs = {...attrs}; + let dynamicPropsPatches: CanNull> = null; + + const normalizedAttrs = {...attrs}; if (Object.isDictionary(normalizedAttrs['v-attrs'])) { normalizedAttrs['v-attrs'] = normalizeComponentAttrs(normalizedAttrs['v-attrs'], dynamicProps, component); @@ -184,22 +182,26 @@ export function normalizeComponentAttrs( return; } + dynamicPropsPatches ??= new Map(); dynamicPropsPatches.set(name, newName); + patchDynamicProps(newName); } function patchDynamicProps(propName: string) { if (functional !== true && component.props[propName]?.forceUpdate === false) { + dynamicPropsPatches ??= new Map(); dynamicPropsPatches.set(propName, ''); } } function modifyDynamicPath() { - if (dynamicProps == null || dynamicPropsPatches.size === 0) { + if (dynamicProps == null || dynamicPropsPatches == null) { return; } - for (let i = dynamicProps.length - 1; i >= 0; i--) { + // eslint-disable-next-line vars-on-top, no-var + for (var i = dynamicProps.length - 1; i >= 0; i--) { const prop = dynamicProps[i], path = dynamicPropsPatches.get(prop); diff --git a/src/core/component/render/helpers/props.ts b/src/core/component/render/helpers/props.ts index f3ef0e64ca..4b06258c66 100644 --- a/src/core/component/render/helpers/props.ts +++ b/src/core/component/render/helpers/props.ts @@ -8,16 +8,23 @@ import { normalizeClass, normalizeStyle } from 'core/component/render/helpers/normalizers'; -export const - isHandler = /^on[^a-z]/; +export const isHandler = { + test(key: string): boolean { + if (key.length < 3 || !key.startsWith('on')) { + return false; + } + + const char = key.charAt(2); + return char.toUpperCase() === char.toLowerCase(); + } +}; /** * Merges the specified props into one and returns a single merged prop object * @param args */ export function mergeProps(...args: Dictionary[]): Dictionary { - const - props: Dictionary = {}; + const props: Dictionary = {}; args.forEach((toMerge) => { for (const key in toMerge) { diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 36bd3e8715..7065e4ae21 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -8,11 +8,11 @@ /* eslint-disable prefer-spread */ -import { app, isComponent, componentRenderFactories, destroyedHooks, ASYNC_RENDER_ID } from 'core/component/const'; +import { app, isComponent, componentRenderFactories, ASYNC_RENDER_ID } from 'core/component/const'; import { attachTemplatesToMeta, ComponentMeta } from 'core/component/meta'; import { isSmartComponent } from 'core/component/reflect'; -import { createVirtualContext } from 'core/component/functional'; +import { createVirtualContext, VHookLifeCycle } from 'core/component/functional'; import type { @@ -48,10 +48,11 @@ import { isHandler, resolveAttrs, - normalizePatchFlagUsingProps, normalizeComponentAttrs, + normalizePatchFlagUsingProps, setVNodePatchFlags, + mergeProps as merge } from 'core/component/render/helpers'; @@ -59,7 +60,7 @@ import { import type { ComponentInterface } from 'core/component/interface'; /** - * Wrapper for the component library `createVNode` function + * A wrapper for the component library `createVNode` function * @param original */ export function wrapCreateVNode(original: T): T { @@ -67,7 +68,7 @@ export function wrapCreateVNode(original: T): T { } /** - * Wrapper for the component library `createElementVNode` function + * A wrapper for the component library `createElementVNode` function * @param original */ export function wrapCreateElementVNode(original: T): T { @@ -78,18 +79,12 @@ export function wrapCreateElementVNode(orig } /** - * Wrapper for the component library `createBlock` function + * A wrapper for the component library `createBlock` function * @param original */ export function wrapCreateBlock(original: T): T { return Object.cast(function wrapCreateBlock(this: ComponentInterface, ...args: Parameters) { - let [ - name, - attrs, - slots, - patchFlag, - dynamicProps - ] = args; + let [name, attrs, slots, patchFlag, dynamicProps] = args; let component: CanNull = null; @@ -151,21 +146,20 @@ export function wrapCreateBlock(original: T): T { attachTemplatesToMeta(component, TPLS[componentName]); } - const virtualCtx = createVirtualContext(component, { - parent: this, - props: attrs, - slots - }); - + const virtualCtx = createVirtualContext(component, {parent: this, props: attrs, slots}); vnode.virtualComponent = virtualCtx; const declaredProps = component.props, - functionalVNode = virtualCtx.render(virtualCtx, []); + filteredAttrs = {}; + + Object.entries(vnode.props).forEach(([key, val]) => { + if (declaredProps[key.camelize(false)] == null) { + filteredAttrs[key] = val; + } + }); - const filteredAttrs = Object.fromEntries( - Object.entries({...vnode.props}).filter(([key]) => declaredProps[key.camelize(false)] == null) - ); + const functionalVNode = virtualCtx.render(virtualCtx, []); vnode.type = functionalVNode.type; vnode.props = merge(filteredAttrs, functionalVNode.props ?? {}); @@ -174,41 +168,16 @@ export function wrapCreateBlock(original: T): T { vnode.dynamicChildren = functionalVNode.dynamicChildren; vnode.dirs = Array.toArray(vnode.dirs, functionalVNode.dirs); + vnode.dirs.push({ - dir: Object.cast(r.resolveDirective.call(virtualCtx, 'hook')), + dir: r.resolveDirective.call(virtualCtx, 'hook'), modifiers: {}, arg: undefined, - value: { - created: (n: Element) => virtualCtx.$emit('[[COMPONENT_HOOK]]', 'created', n), - - beforeMount: (n: Element) => virtualCtx.$emit('[[COMPONENT_HOOK]]', 'beforeMount', n), - mounted: (n: Element) => virtualCtx.$emit('[[COMPONENT_HOOK]]', 'mounted', n), - - beforeUpdate: (n: Element) => virtualCtx.$emit('[[COMPONENT_HOOK]]', 'beforeUpdate', n), - updated: (n: Element) => virtualCtx.$emit('[[COMPONENT_HOOK]]', 'updated', n), - - beforeUnmount: (n: Element) => { - // A component might have already been removed by explicitly calling $destroy - if (destroyedHooks[virtualCtx.hook] != null) { - return; - } - - virtualCtx.$emit('[[COMPONENT_HOOK]]', 'beforeDestroy', n); - }, - - unmounted: (n: Element) => { - // A component might have already been removed by explicitly calling $destroy - if (destroyedHooks[virtualCtx.hook] != null) { - return; - } - - virtualCtx.$emit('[[COMPONENT_HOOK]]', 'destroyed', n); - } - }, - + value: new VHookLifeCycle(virtualCtx), oldValue: undefined, + instance: Object.cast(virtualCtx) }); @@ -222,11 +191,13 @@ export function wrapCreateBlock(original: T): T { vnode.patchFlag |= functionalVNode.patchFlag; } - if (!SSR && Object.size(functionalVNode.dynamicProps) > 0) { - vnode.dynamicProps ??= []; - functionalVNode.dynamicProps?.forEach((propName) => { + if (!SSR && functionalVNode.dynamicProps != null && functionalVNode.dynamicProps.length > 0) { + const dynamicProps = vnode.dynamicProps ?? []; + vnode.dynamicProps = dynamicProps; + + functionalVNode.dynamicProps.forEach((propName) => { if (isHandler.test(propName)) { - vnode.dynamicProps!.push(propName); + dynamicProps.push(propName); setVNodePatchFlags(vnode, 'props'); } }); @@ -243,7 +214,7 @@ export function wrapCreateBlock(original: T): T { } /** - * Wrapper for the component library `createElementBlock` function + * A wrapper for the component library `createElementBlock` function * @param original */ export function wrapCreateElementBlock(original: T): T { @@ -254,7 +225,7 @@ export function wrapCreateElementBlock(orig } /** - * Wrapper for the component library `resolveComponent` or `resolveDynamicComponent` functions + * A wrapper for the component library `resolveComponent` or `resolveDynamicComponent` functions * @param original */ export function wrapResolveComponent( @@ -282,7 +253,7 @@ export function wrapResolveComponent( @@ -295,7 +266,7 @@ export function wrapResolveDirective( } /** - * Wrapper for the component library `mergeProps` function + * A wrapper for the component library `mergeProps` function * @param original */ export function wrapMergeProps(original: T): T { @@ -311,7 +282,7 @@ export function wrapMergeProps(original: T): T { } /** - * Wrapper for the component library `renderList` function + * A wrapper for the component library `renderList` function * * @param original * @param withCtx @@ -319,7 +290,7 @@ export function wrapMergeProps(original: T): T { export function wrapRenderList(original: T, withCtx: C): T { return Object.cast(function renderList( this: ComponentInterface, - src: Iterable | Dictionary | number | undefined | null, + src: Nullable | Dictionary | number>, cb: AnyFunction ) { const @@ -348,7 +319,7 @@ export function wrapRenderList(original: T): T { @@ -360,13 +331,7 @@ export function wrapRenderSlot(original: T): T { return original.apply(null, args); } catch { - const [ - slots, - name, - props, - fallback - ] = args; - + const [slots, name, props, fallback] = args; const children = slots[name]?.(props) ?? fallback?.() ?? []; return r.createBlock.call(this, r.Fragment, {key: props?.key ?? `_${name}`}, children); } @@ -377,7 +342,7 @@ export function wrapRenderSlot(original: T): T { } /** - * Wrapper for the component library `withCtx` function + * A wrapper for the component library `withCtx` function * @param original */ export function wrapWithCtx(original: T): T { @@ -392,16 +357,17 @@ export function wrapWithCtx(original: T): T { // If the original function expects more arguments than provided, we explicitly set them to `undefined`, // to then add another, "unregistered" argument if (fn.length - args.length > 0) { - args = args.concat(new Array(fn.length - args.length).fill(undefined)); + args.push(...new Array(fn.length - args.length).fill(undefined)); } - return fn(...args.concat(args[0])); + args.push(args[0]); + return fn(...args); }); }); } /** - * Wrapper for the component library `withDirectives` function + * A wrapper for the component library `withDirectives` function * @param _ */ export function wrapWithDirectives(_: T): T { @@ -442,8 +408,7 @@ export function wrapWithDirectives(_: T): T { } if (Object.isFunction(dir.beforeCreate)) { - const - newVnode = dir.beforeCreate(binding, vnode); + const newVnode = dir.beforeCreate(binding, vnode); if (newVnode != null) { vnode = newVnode; From bbdd3f5c7186e559eea8f1f0f35a820d6550fc0c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 26 Aug 2024 16:21:54 +0300 Subject: [PATCH 080/334] fix: fixes after refactoring --- src/core/component/render/helpers/props.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/component/render/helpers/props.ts b/src/core/component/render/helpers/props.ts index 4b06258c66..197c6af2e4 100644 --- a/src/core/component/render/helpers/props.ts +++ b/src/core/component/render/helpers/props.ts @@ -14,8 +14,10 @@ export const isHandler = { return false; } - const char = key.charAt(2); - return char.toUpperCase() === char.toLowerCase(); + const codePoint = key.codePointAt(2); + + // [^a-z] + return codePoint != null && (codePoint < 97 || codePoint > 122); } }; From 513bed766dccd862f5525e5b8324e42fb7bf80ea Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 29 Aug 2024 12:40:58 +0300 Subject: [PATCH 081/334] chore: moved async to v4/core --- src/core/async/CHANGELOG.md | 16 -- src/core/async/README.md | 27 --- src/core/async/const.ts | 20 -- src/core/async/helpers.ts | 50 ----- src/core/async/index.ts | 358 ------------------------------------ src/core/async/interface.ts | 53 ------ yarn.lock | 4 +- 7 files changed, 2 insertions(+), 526 deletions(-) delete mode 100644 src/core/async/CHANGELOG.md delete mode 100644 src/core/async/README.md delete mode 100644 src/core/async/const.ts delete mode 100644 src/core/async/helpers.ts delete mode 100644 src/core/async/index.ts delete mode 100644 src/core/async/interface.ts diff --git a/src/core/async/CHANGELOG.md b/src/core/async/CHANGELOG.md deleted file mode 100644 index 76418ffae2..0000000000 --- a/src/core/async/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -Changelog -========= - -> **Tags:** -> - :boom: [Breaking Change] -> - :rocket: [New Feature] -> - :bug: [Bug Fix] -> - :memo: [Documentation] -> - :house: [Internal] -> - :nail_care: [Polish] - -## v3.0.0-rc.37 (2020-07-20) - -#### :house: Internal - -* Fixed ESLint warnings diff --git a/src/core/async/README.md b/src/core/async/README.md deleted file mode 100644 index 5762d16aa3..0000000000 --- a/src/core/async/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# core/async - -This module extends the [Async](https://v4fire.github.io/Core/modules/src_core_async_index.html) class from and adds a bunch of methods for browser-specific async tasks. - -```js -import Async from 'core/async' - -const $a = new Async(); - -$a.requestAnimationFrame(() => { - console.log('Boom!'); -}); - -$a.dnd(document.getElementById('bla'), { - onDragStart() { - - }, - - onDrag() { - - }, - - onDragEnd() { - - } -}); -``` diff --git a/src/core/async/const.ts b/src/core/async/const.ts deleted file mode 100644 index a0ed10b47c..0000000000 --- a/src/core/async/const.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import { namespaces as superNamespaces } from '@v4fire/core/core/async/const'; -import { ClientNamespaces } from 'core/async/interface'; - -export const namespaces = { - ...superNamespaces, - ...Object.convertEnumToDict(ClientNamespaces) -}; - -export type NamespacesDictionary = typeof namespaces; - -export const - unsuspendRgxp = /:!suspend(?:\b|$)/; diff --git a/src/core/async/helpers.ts b/src/core/async/helpers.ts deleted file mode 100644 index 913eb85d89..0000000000 --- a/src/core/async/helpers.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import { unsuspendRgxp } from 'core/async/const'; -import type { AsyncOptions } from 'core/async/interface'; - -/** - * Takes an object with async options and returns a new one with a modified group to support task suspending. - * To prevent suspending provide a group with the `:!suspend` modifier. - * - * @param opts - * @param [groupMod] - additional group modifier - * - * @example - * ```js - * // {label: 'foo'} - * console.log(wrapWithSuspending({label: 'foo'})); - * - * // {group: ':baz:suspend', label: 'foo'} - * console.log(wrapWithSuspending({label: 'foo'}), 'baz'); - * - * // {group: 'bar:suspend'} - * console.log(wrapWithSuspending({group: 'bar'})); - * - * // {group: 'bar:baz:suspend'} - * console.log(wrapWithSuspending({group: 'bar'}, 'baz')); - * - * // {group: 'bar:!suspend'} - * console.log(wrapWithSuspending({group: 'bar:!suspend'}, 'baz')) - * ``` - */ -export function wrapWithSuspending(opts: T, groupMod?: string): T { - let - group = Object.isPlainObject(opts) ? opts.group : null; - - if (groupMod != null) { - group = `${group ?? ''}:${groupMod}`; - } - - if (group == null || RegExp.test(unsuspendRgxp, group)) { - return opts; - } - - return {...opts, group: `${group}:suspend`}; -} diff --git a/src/core/async/index.ts b/src/core/async/index.ts deleted file mode 100644 index 2933ceab1a..0000000000 --- a/src/core/async/index.ts +++ /dev/null @@ -1,358 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -/** - * [[include:core/async/README.md]] - * @packageDocumentation - */ - -import SyncPromise from 'core/promise/sync'; -import Super, { AsyncCbOptions, ClearOptionsId, isAsyncOptions } from '@v4fire/core/core/async'; -import { namespaces, NamespacesDictionary } from 'core/async/const'; - -import type { - - AsyncRequestAnimationFrameOptions, - AsyncAnimationFrameOptions, - AsyncDnDOptions, - DnDEventOptions, - AsyncCb, - AnimationFrameCb - -} from 'core/async/interface'; - -export * from '@v4fire/core/core/async'; -export * from 'core/async/const'; -export * from 'core/async/helpers'; -export * from 'core/async/interface'; - -export default class Async> extends Super { - static override namespaces: NamespacesDictionary = namespaces; - - /** - * Wrapper for requestAnimationFrame - * - * @param cb - callback function - * @param [element] - link for the element - */ - requestAnimationFrame(cb: AnimationFrameCb, element?: Element): Nullable; - - /** - * Wrapper for requestAnimationFrame - * - * @param cb - callback function - * @param opts - additional options for the operation - */ - requestAnimationFrame( - cb: AnimationFrameCb, - opts: AsyncRequestAnimationFrameOptions - ): Nullable; - - requestAnimationFrame( - cb: AnimationFrameCb, - p?: Element | AsyncRequestAnimationFrameOptions - ): Nullable { - if (Object.isPlainObject(p)) { - return this.registerTask({ - ...p, - name: this.namespaces.animationFrame, - obj: cb, - clearFn: cancelAnimationFrame, - wrapper: requestAnimationFrame, - linkByWrapper: true, - args: p.element - }); - } - - return this.registerTask({ - name: this.namespaces.animationFrame, - obj: cb, - clearFn: cancelAnimationFrame, - wrapper: requestAnimationFrame, - linkByWrapper: true, - args: p - }); - } - - /** - * Wrapper for cancelAnimationFrame - * - * @alias - * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) - */ - cancelAnimationFrame(id?: number): this; - - /** - * Clears the specified "requestAnimationFrame" timer or a group of timers - * @param opts - options for the operation - */ - cancelAnimationFrame(opts: ClearOptionsId): this; - cancelAnimationFrame(task?: number | ClearOptionsId): this { - return this.clearAnimationFrame(Object.cast(task)); - } - - /** - * Wrapper for cancelAnimationFrame - * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) - */ - clearAnimationFrame(id?: number): this; - - /** - * Clears the specified "requestAnimationFrame" timer or a group of timers - * @param opts - options for the operation - */ - clearAnimationFrame(opts: ClearOptionsId): this; - clearAnimationFrame(task?: number | ClearOptionsId): this { - return this - .cancelTask(task, this.namespaces.animationFrame) - .cancelTask(task, this.namespaces.animationFramePromise); - } - - /** - * Mutes the specified "requestAnimationFrame" timer - * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) - */ - muteAnimationFrame(id?: number): this; - - /** - * Mutes the specified "requestAnimationFrame" timer or a group of timers - * @param opts - options for the operation - */ - muteAnimationFrame(opts: ClearOptionsId): this; - muteAnimationFrame(task?: number | ClearOptionsId): this { - return this.markTask('muted', task, this.namespaces.animationFrame); - } - - /** - * Unmutes the specified "requestAnimationFrame" timer - * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) - */ - unmuteAnimationFrame(id?: number): this; - - /** - * Unmutes the specified "requestAnimationFrame" timer or a group of timers - * @param opts - options for the operation - */ - unmuteAnimationFrame(opts: ClearOptionsId): this; - unmuteAnimationFrame(task?: number | ClearOptionsId): this { - return this.markTask('!muted', task, this.namespaces.animationFrame); - } - - /** - * Suspends the specified "requestAnimationFrame" timer - * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) - */ - suspendAnimationFrame(id?: number): this; - - /** - * Suspends the specified "requestAnimationFrame" timer or a group of timers - * @param opts - options for the operation - */ - suspendAnimationFrame(opts: ClearOptionsId): this; - suspendAnimationFrame(task?: number | ClearOptionsId): this { - return this.markTask('paused', task, this.namespaces.animationFrame); - } - - /** - * Unsuspends the specified "requestAnimationFrame" timer - * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) - */ - unsuspendAnimationFrame(id?: number): this; - - /** - * Unsuspends the specified "requestAnimationFrame" timer or a group of timers - * @param opts - options for the operation - */ - unsuspendAnimationFrame(opts: ClearOptionsId): this; - unsuspendAnimationFrame(task?: number | ClearOptionsId): this { - return this.markTask('!paused', task, this.namespaces.animationFrame); - } - - /** - * Returns a promise that will be resolved on the next animation frame request - * @param [element] - link for the element - */ - animationFrame(element?: Element): SyncPromise; - - /** - * Returns a promise that will be resolved on the next animation frame request - * @param opts - options for the operation - */ - animationFrame(opts: AsyncAnimationFrameOptions): SyncPromise; - animationFrame(p?: Element | AsyncAnimationFrameOptions): SyncPromise { - return new SyncPromise((resolve, reject) => { - if (Object.isPlainObject(p)) { - return this.requestAnimationFrame(resolve, { - ...p, - promise: true, - element: p.element, - onClear: >this.onPromiseClear(resolve, reject) - }); - } - - return this.requestAnimationFrame(resolve, { - promise: true, - element: p, - onClear: >this.onPromiseClear(resolve, reject) - }); - }); - } - - /** - * Adds Drag&Drop listeners to the specified element - * - * @param el - * @param [useCapture] - */ - dnd(el: Element, useCapture?: boolean): Nullable; - - /** - * Adds Drag&Drop listeners to the specified element - * - * @param el - * @param opts - options for the operation - */ - dnd(el: Element, opts: AsyncDnDOptions): Nullable; - dnd(el: Element, opts?: boolean | AsyncDnDOptions): Nullable { - let - useCapture, - p: AsyncDnDOptions & AsyncCbOptions; - - if (isAsyncOptions>(opts)) { - useCapture = opts.options?.capture; - p = opts; - - } else { - useCapture = opts; - p = {}; - } - - p.group = p.group ?? `dnd:${Math.random()}`; - - if (this.locked) { - return null; - } - - const clearHandlers = Array.toArray(p.onClear); - p.onClear = clearHandlers; - - function dragStartClear(this: unknown, ...args: unknown[]): void { - for (let i = 0; i < clearHandlers.length; i++) { - clearHandlers[i].call(this, ...args, 'dragstart'); - } - } - - function dragClear(this: unknown, ...args: unknown[]): void { - for (let i = 0; i < clearHandlers.length; i++) { - clearHandlers[i].call(this, ...args, 'drag'); - } - } - - function dragEndClear(this: unknown, ...args: unknown[]): void { - for (let i = 0; i < clearHandlers.length; i++) { - clearHandlers[i].call(this, ...args, 'dragend'); - } - } - - const dragStartUseCapture = !p.onDragStart || Object.isSimpleFunction(p.onDragStart) ? - useCapture : - Boolean(p.onDragStart.capture); - - const dragUseCapture = !p.onDrag || Object.isSimpleFunction(p.onDrag) ? - useCapture : - Boolean(p.onDrag.capture); - - const dragEndUseCapture = !p.onDragEnd || Object.isSimpleFunction(p.onDragEnd) ? - useCapture : - Boolean(p.onDragEnd.capture); - - const - that = this, - asyncOpts = {join: p.join, label: p.label, group: p.group}; - - function dragStart(this: CTX, e: Event): void { - e.preventDefault(); - - let - res; - - if (p.onDragStart) { - if (Object.isFunction(p.onDragStart)) { - res = p.onDragStart.call(this, e, el); - - } else if (Object.isPlainObject(p.onDragStart)) { - res = (p.onDragStart).handler.call(this, e, el); - } - } - - const drag = (e) => { - e.preventDefault(); - - if (res !== false) { - if (Object.isFunction(p.onDrag)) { - res = p.onDrag.call(this, e, el); - - } else if (Object.isPlainObject(p.onDrag)) { - res = (p.onDrag).handler.call(this, e, el); - } - } - }; - - const - links: object[] = []; - - { - const - e = ['mousemove', 'touchmove']; - - for (let i = 0; i < e.length; i++) { - const - link = that.on(document, e[i], drag, {...asyncOpts, onClear: dragClear}, dragUseCapture); - - if (link) { - links.push(link); - } - } - } - - const dragEnd = (e) => { - e.preventDefault(); - - if (res !== false) { - if (Object.isFunction(p.onDragEnd)) { - res = p.onDragEnd.call(this, e, el); - - } else if (Object.isPlainObject(p.onDragEnd)) { - res = (p.onDragEnd).handler.call(this, e, el); - } - } - - for (let i = 0; i < links.length; i++) { - that.off(links[i]); - } - }; - - { - const - e = ['mouseup', 'touchend']; - - for (let i = 0; i < e.length; i++) { - const - link = that.on(document, e[i], dragEnd, {...asyncOpts, onClear: dragEndClear}, dragEndUseCapture); - - if (link) { - links.push(link); - } - } - } - } - - this.on(el, 'mousedown touchstart', dragStart, {...asyncOpts, onClear: dragStartClear}, dragStartUseCapture); - return p.group; - } -} diff --git a/src/core/async/interface.ts b/src/core/async/interface.ts deleted file mode 100644 index 1fd4e485a4..0000000000 --- a/src/core/async/interface.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import type { - - Namespace as SuperNamespace, - - AsyncOptions, - AsyncCbOptions, - AsyncOnOptions, - - ProxyCb - -} from '@v4fire/core/core/async/interface'; - -import type Async from 'core/async'; - -export * from '@v4fire/core/core/async/interface'; - -export enum ClientNamespaces { - animationFrame, - animationFramePromise -} - -export type ClientNamespace = keyof typeof ClientNamespaces; -export type Namespace = SuperNamespace | ClientNamespace; - -export interface AsyncRequestAnimationFrameOptions extends AsyncCbOptions { - element?: Element; -} - -export interface AsyncAnimationFrameOptions extends AsyncOptions { - element?: Element; -} - -export interface AsyncDnDOptions extends AsyncOnOptions { - onDragStart?: DnDCb | DnDEventOptions; - onDrag?: DnDCb | DnDEventOptions; - onDragEnd?: DnDCb | DnDEventOptions; -} - -export type DnDCb = Function | ((this: CTX, e: MouseEvent, el: Node) => R); -export type AnimationFrameCb = ProxyCb; - -export interface DnDEventOptions { - capture?: boolean; - handler: DnDCb; -} diff --git a/yarn.lock b/yarn.lock index 03376ef08a..d9994dc027 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5991,7 +5991,7 @@ __metadata: "@v4fire/core@github:V4Fire/Core#speedup": version: 4.0.0-alpha.43 - resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=4cc4500ac07bc7198d6258701626b338ece4a301" + resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=0fac26de2e0b88ac83a90c866e6041d78ca517b5" dependencies: "@babel/core": "npm:7.17.5" "@babel/helper-module-transforms": "npm:7.16.7" @@ -6134,7 +6134,7 @@ __metadata: optional: true xhr2: optional: true - checksum: 7eae4a85334424bcf4ab1c65a0ef4cdb8806f709f683c3483cd2f685264ab7ae59090b8c89f0798f6838481de9dd09b3b4feec45513c23f52405e0fe131c11f3 + checksum: a5362a61e86ee1e8b79d2c44146e6244c64a14183304c31746aa3201029a448bfe3971412fa9e78bf43621ffef464ca42fd6a59bdb864fcc74ef4926fbe8539f languageName: node linkType: hard From a6b5d7bdef3d0cd4104efa2ec148e6a8b7f30515 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 29 Aug 2024 12:42:16 +0300 Subject: [PATCH 082/334] chore: optimization --- src/core/component/hook/index.ts | 15 ++++++++++++++- .../component/interface/component/component.ts | 5 +++++ src/core/component/interface/component/unsafe.ts | 3 +++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/core/component/hook/index.ts b/src/core/component/hook/index.ts index 690cfe125f..8017aba53b 100644 --- a/src/core/component/hook/index.ts +++ b/src/core/component/hook/index.ts @@ -114,7 +114,20 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn const tasks: Array> = []; hooks.slice().forEach((hook) => { - const res = args.length > 0 ? hook.fn.apply(component, args) : hook.fn.call(component); + let res: unknown; + + switch (args.length) { + case 0: + res = hook.fn.call(component); + break; + + case 1: + res = hook.fn.call(component, args[0]); + break; + + default: + res = hook.fn.apply(component, args); + } if (hook.once) { hooks.pop(); diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index 52edd75e4a..7403689297 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -295,6 +295,11 @@ export abstract class ComponentInterface { */ protected readonly $async!: Async; + /** + * A list of functions that should be called when the component is destroyed + */ + protected readonly $destructors!: Function[]; + /** * Cache for rendered SSR templates */ diff --git a/src/core/component/interface/component/unsafe.ts b/src/core/component/interface/component/unsafe.ts index eb368bc6ac..c424c94010 100644 --- a/src/core/component/interface/component/unsafe.ts +++ b/src/core/component/interface/component/unsafe.ts @@ -57,6 +57,9 @@ export interface UnsafeComponentInterface Date: Thu, 29 Aug 2024 12:48:08 +0300 Subject: [PATCH 083/334] fix: fixes after updating async --- .../super/i-block/modules/activation/const.ts | 12 +++++------- .../super/i-block/modules/activation/index.ts | 17 ++++++++--------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/components/super/i-block/modules/activation/const.ts b/src/components/super/i-block/modules/activation/const.ts index 3a35e3e9f5..48d672c1d3 100644 --- a/src/components/super/i-block/modules/activation/const.ts +++ b/src/components/super/i-block/modules/activation/const.ts @@ -6,11 +6,9 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import Async from 'core/async'; +import { Namespaces } from 'core/async'; -export const - suspendRgxp = /:suspend(?:\b|$)/, - asyncNames = Async.linkNames; +export const suspendRgxp = /:suspend(?:\b|$)/; export const inactiveStatuses: Dictionary = Object.createDict({ destroyed: true, @@ -23,7 +21,7 @@ export const readyStatuses: Dictionary = Object.createDict({ ready: true }); -export const nonMuteAsyncLinkNames: Dictionary = Object.createDict({ - [asyncNames.promise]: true, - [asyncNames.request]: true +export const nonMuteAsyncNamespaces: Dictionary = Object.createDict({ + [Namespaces.promise]: true, + [Namespaces.request]: true }); diff --git a/src/components/super/i-block/modules/activation/index.ts b/src/components/super/i-block/modules/activation/index.ts index 9ebdb74e95..e397728938 100644 --- a/src/components/super/i-block/modules/activation/index.ts +++ b/src/components/super/i-block/modules/activation/index.ts @@ -13,7 +13,9 @@ import symbolGenerator from 'core/symbol'; +import { Namespaces } from 'core/async'; import { unwrap } from 'core/object/watch'; + import { runHook, callMethodFromComponent } from 'core/component'; import type iBlock from 'components/super/i-block/i-block'; @@ -22,12 +24,9 @@ import { statuses } from 'components/super/i-block/const'; import { suspendRgxp, - readyStatuses, inactiveStatuses, - - asyncNames, - nonMuteAsyncLinkNames + nonMuteAsyncNamespaces } from 'components/super/i-block/modules/activation/const'; @@ -220,15 +219,15 @@ export function onDeactivated(component: iBlock): void { ]; async.forEach(($a) => { - Object.keys(asyncNames).forEach((key) => { - if (nonMuteAsyncLinkNames[key]) { + Object.entries(Namespaces).forEach(([key, namespace]) => { + if (Object.isNumber(namespace) && nonMuteAsyncNamespaces[namespace]) { return; } - const fn = $a[`mute-${asyncNames[key]}`.camelize(false)]; + const method = $a[`mute-${key}`.camelize(false)]; - if (Object.isFunction(fn)) { - fn.call($a); + if (Object.isFunction(method)) { + method.call($a); } }); From 439275c6954907fa9b122378d337fc3ee367cb1d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 29 Aug 2024 12:50:18 +0300 Subject: [PATCH 084/334] chore: optimization --- src/components/super/i-block/base/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 021e73fafc..6ee4afa480 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -35,7 +35,7 @@ import { getComponentName, bindRemoteWatchers, - customWatcherRgxp, + isCustomWatcher, RawWatchHandler, WatchPath, @@ -477,7 +477,7 @@ export default abstract class iBlockBase extends iBlockFriends { opts = Object.isDictionary(optsOrHandler) ? optsOrHandler : {}; } - if (Object.isString(path) && RegExp.test(customWatcherRgxp, path)) { + if (Object.isString(path) && isCustomWatcher.test(path)) { bindRemoteWatchers(this, { async: $a, watchers: { From 37359759ceecc300afc26b20ca09c07e4acc7299 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 29 Aug 2024 13:50:49 +0300 Subject: [PATCH 085/334] chore: using fast destructors --- .../super/i-block/providers/index.ts | 2 +- src/core/component/accessor/index.ts | 9 ++++--- src/core/component/event/component.ts | 2 +- .../component/init/states/before-create.ts | 3 ++- .../component/init/states/before-destroy.ts | 6 ++--- src/core/component/init/states/created.ts | 17 ++++-------- src/core/component/meta/create.ts | 5 +--- src/core/component/prop/helpers.ts | 7 ++--- src/core/component/watch/create.ts | 20 +++++++------- src/core/component/watch/helpers.ts | 26 +++++++------------ 10 files changed, 39 insertions(+), 58 deletions(-) diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index e98350cf6a..7575fe297b 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -343,7 +343,7 @@ export default abstract class iBlockProviders extends iBlockState { return dp; function registerDestructor() { - that.r.unsafe.async.worker(() => { + that.r.unsafe.$destructors.push(() => { instanceCache[dp.getCacheKey()]?.destroy(); }); } diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index d474ad2c19..7816c697db 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -65,11 +65,12 @@ import type { ComponentInterface } from 'core/component/interface'; */ export function attachAccessorsFromMeta(component: ComponentInterface): void { const { - async: $a, - meta, + // eslint-disable-next-line deprecation/deprecation - meta: {params: {deprecatedProps}, tiedFields} + meta: {params: {deprecatedProps}, tiedFields}, + + $destructors } = component.unsafe; const isFunctional = meta.params.functional === true; @@ -182,7 +183,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { }); // Register a worker to clean up memory upon component destruction - $a.worker(() => { + $destructors.push(() => { // eslint-disable-next-line require-yield gc.add(function* destructor() { cachedAccessors.forEach((getter) => { diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index 7035d6a2d8..066c22ccfe 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -114,7 +114,7 @@ export function implementEventEmitterAPI(component: object): void { value: getMethod('off') }); - ctx.$async.worker(() => { + ctx.$destructors.push(() => { gc.add(function* destructor() { for (const key of ['$emit', '$on', '$once', '$off']) { Object.defineProperty(ctx, key, { diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index e48d631381..a43d5afb2f 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -71,6 +71,7 @@ export function beforeCreateState( unsafe.async = new Async(component); unsafe.$async = new Async(component); + unsafe.$destructors = []; Object.defineProperty(unsafe, '$destroy', { configurable: true, @@ -230,7 +231,7 @@ export function beforeCreateState( } }); - unsafe.$async.worker(() => { + unsafe.$destructors.push(() => { // eslint-disable-next-line require-yield gc.add(function* destructor() { for (const key of ['$root', '$parent', '$normalParent', '$children']) { diff --git a/src/core/component/init/states/before-destroy.ts b/src/core/component/init/states/before-destroy.ts index c7e8437d50..76513140a2 100644 --- a/src/core/component/init/states/before-destroy.ts +++ b/src/core/component/init/states/before-destroy.ts @@ -31,10 +31,7 @@ export function beforeDestroyState(component: ComponentInterface, opts: Componen runHook('beforeDestroy', component).catch(stderr); callMethodFromComponent(component, 'beforeDestroy'); - const { - unsafe, - unsafe: {$el} - } = component; + const {unsafe, unsafe: {$el}} = component; unsafe.$emit('[[BEFORE_DESTROY]]', >{ recursive: opts.recursive ?? true, @@ -43,6 +40,7 @@ export function beforeDestroyState(component: ComponentInterface, opts: Componen unsafe.async.clearAll().locked = true; unsafe.$async.clearAll().locked = true; + unsafe.$destructors.forEach((destructor) => destructor()); if ($el != null && $el.component === component) { delete $el.component; diff --git a/src/core/component/init/states/created.ts b/src/core/component/init/states/created.ts index 4798e0e72d..533eedad58 100644 --- a/src/core/component/init/states/created.ts +++ b/src/core/component/init/states/created.ts @@ -25,14 +25,7 @@ export function createdState(component: ComponentInterface): void { return; } - const { - unsafe, - unsafe: { - $root: r, - $async: $a, - $parent: parent - } - } = component; + const {unsafe, unsafe: {$parent: parent}} = component; unmute(unsafe.$fields); unmute(unsafe.$systemFields); @@ -40,7 +33,7 @@ export function createdState(component: ComponentInterface): void { if (parent != null) { const isRegularComponent = unsafe.meta.params.functional !== true, - isDynamicallyMountedComponent = '$remoteParent' in r; + isDynamicallyMountedComponent = '$remoteParent' in unsafe.r; const destroy = (opts: Required) => { // A component might have already been removed by explicitly calling $destroy @@ -55,7 +48,7 @@ export function createdState(component: ComponentInterface): void { parent.unsafe.$once('[[BEFORE_DESTROY]]', destroy); - unsafe.$async.worker(() => { + unsafe.$destructors.push(() => { // A component might have already been removed by explicitly calling $destroy if (destroyedHooks[parent.hook] != null) { return; @@ -80,7 +73,7 @@ export function createdState(component: ComponentInterface): void { return; } - $a.requestIdleCallback(component.activate.bind(component), { + unsafe.$async.requestIdleCallback(component.activate.bind(component), { label: remoteActivationLabel, timeout: 50 }); @@ -93,7 +86,7 @@ export function createdState(component: ComponentInterface): void { } normalParent.$on('onHookChange', onActivation); - $a.worker(() => normalParent.$off('onHookChange', onActivation)); + unsafe.$destructors.push(() => normalParent.$off('onHookChange', onActivation)); } } diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index f3d56b6f51..97d7305adc 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -111,10 +111,7 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { if (needCacheRenderFn) { cache.set(ctx, render); - - unsafe.$async.worker(() => { - cache.delete(ctx); - }, {label}); + unsafe.$async.worker(() => cache.delete(ctx), {label}); } return render(); diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index 0547e9e06b..8c0ffe60d2 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -17,10 +17,7 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function attachAttrPropsListeners(component: ComponentInterface): void { - const { - unsafe, - unsafe: {meta} - } = component; + const {unsafe, unsafe: {meta}} = component; if (unsafe.meta.params.functional === true) { return; @@ -67,7 +64,7 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { if (propValuesToUpdate.length > 0) { nonFunctionalParent.$on('hook:beforeUpdate', updatePropsValues); - unsafe.$async.worker(() => nonFunctionalParent.$off('hook:beforeUpdate', updatePropsValues)); + unsafe.$destructors.push(() => nonFunctionalParent.$off('hook:beforeUpdate', updatePropsValues)); } async function updatePropsValues() { diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index a3a8c193ef..32fa890c2f 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -149,7 +149,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface cacheKey = [info.originalPath]; } else { - cacheKey = Array.concat([info.ctx], info.path); + cacheKey = Array.toArray(info.ctx, Object.cast(info.path)); } if (Object.has(watchCache, cacheKey)) { @@ -269,7 +269,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface }; } else if (flush === 'post') { - handler = (...args) => component.$nextTick().then(() => originalHandler.call(this, ...args)); + handler = (...args: unknown[]) => component.$nextTick().then(() => originalHandler.call(this, ...args)); } if (needImmediate) { @@ -595,16 +595,18 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface function wrapDestructor(destructor: T): T { if (Object.isFunction(destructor)) { - // Every worker passed to Async has a counter that tracks the number of consumers of this worker. - // However, in this case, this behavior is redundant and could lead to an error. - // That's why we wrap the original destructor with a new function. - component.unsafe.$async.worker(() => { - watchCache.clear(); - return destructor(); - }); + component.unsafe.$destructors.push(wrappedDestructor); } return destructor; + + function wrappedDestructor() { + watchCache.clear(); + + if (Object.isFunction(destructor)) { + return destructor(); + } + } } }; } diff --git a/src/core/component/watch/helpers.ts b/src/core/component/watch/helpers.ts index 0dec9e696a..35a1f660e3 100644 --- a/src/core/component/watch/helpers.ts +++ b/src/core/component/watch/helpers.ts @@ -33,16 +33,14 @@ export function attachDynamicWatcher( ): Function { // eslint-disable-next-line @typescript-eslint/typedef const wrapper = function wrapper(this: unknown, mutations, ...args) { - const - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - isPacked = args.length === 0; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + const isPacked = args.length === 0; if (!isPacked) { mutations = [Object.cast([mutations, ...args])]; } - const - filteredMutations: unknown[] = []; + const filteredMutations: unknown[] = []; mutations.forEach((mutation) => { const @@ -92,23 +90,20 @@ export function attachDynamicWatcher( }; } else { - let - handlersStore = store.get(prop.ctx); + let handlersStore = store.get(prop.ctx); if (!handlersStore) { handlersStore = Object.createDict(); store.set(prop.ctx, handlersStore); } - const - nm = prop.accessor ?? prop.name; + const name = prop.accessor ?? prop.name; - let - handlersSet = handlersStore[nm]; + let handlersSet = handlersStore[name]; - if (!handlersSet) { + if (handlersSet == null) { handlersSet = new Set(); - handlersStore[nm] = handlersSet; + handlersStore[name] = handlersSet; } handlersSet.add(wrapper); @@ -118,10 +113,7 @@ export function attachDynamicWatcher( }; } - // Every worker that passed to Async have a counter with a number of consumers of this worker, - // but in this case this behaviour is redundant and can produce an error, - // that why we wrap original destructor with a new function - component.unsafe.$async.worker(() => destructor()); + component.unsafe.$destructors.push(destructor); return destructor; } From de891bd400193c7edfef0ca0559e1c6679ec6ad6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 29 Aug 2024 14:13:06 +0300 Subject: [PATCH 086/334] chore: updated dependencies --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index d9994dc027..139f01aae0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5991,7 +5991,7 @@ __metadata: "@v4fire/core@github:V4Fire/Core#speedup": version: 4.0.0-alpha.43 - resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=0fac26de2e0b88ac83a90c866e6041d78ca517b5" + resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=5fee52d1f96d493d553e4cc8147a1da6598d3841" dependencies: "@babel/core": "npm:7.17.5" "@babel/helper-module-transforms": "npm:7.16.7" @@ -6134,7 +6134,7 @@ __metadata: optional: true xhr2: optional: true - checksum: a5362a61e86ee1e8b79d2c44146e6244c64a14183304c31746aa3201029a448bfe3971412fa9e78bf43621ffef464ca42fd6a59bdb864fcc74ef4926fbe8539f + checksum: 009ad9cc22525b1d74f3cdd07434c3a62a901910dd3b5ab17785c80f1082bb7e0f8c8457f191b1ab7d9d9d6b3d75d7dee8d507a717f88afb52043a3251c96ac9 languageName: node linkType: hard From 2aaf614b9a36f273cfed5345db95441d16c5147e Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 29 Aug 2024 14:39:27 +0300 Subject: [PATCH 087/334] chore: updated dependencies --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 139f01aae0..d996237d0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5991,7 +5991,7 @@ __metadata: "@v4fire/core@github:V4Fire/Core#speedup": version: 4.0.0-alpha.43 - resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=5fee52d1f96d493d553e4cc8147a1da6598d3841" + resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=db2d9b361576554930d51f6b088a8c325dfb3f92" dependencies: "@babel/core": "npm:7.17.5" "@babel/helper-module-transforms": "npm:7.16.7" @@ -6134,7 +6134,7 @@ __metadata: optional: true xhr2: optional: true - checksum: 009ad9cc22525b1d74f3cdd07434c3a62a901910dd3b5ab17785c80f1082bb7e0f8c8457f191b1ab7d9d9d6b3d75d7dee8d507a717f88afb52043a3251c96ac9 + checksum: 34c4df68b30106d7554fb6f35204f46d75103c48ffc5d9af0ba78bc2e9c3fc2bbe3de8a5f4eec21ac484f31b7a0eac352203cdf6a79d8eb6e1c1a1175b4e9c69 languageName: node linkType: hard From ab0085de20f9fd3b25f82098f0edbf1b1661bc74 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 29 Aug 2024 15:36:50 +0300 Subject: [PATCH 088/334] chore: fixed tests --- src/core/component/event/test/unit/main.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/component/event/test/unit/main.ts b/src/core/component/event/test/unit/main.ts index 8f0954d48e..29d24552de 100644 --- a/src/core/component/event/test/unit/main.ts +++ b/src/core/component/event/test/unit/main.ts @@ -28,7 +28,8 @@ test.describe('core/component/event', () => { // ... }); - return Object.get(ctx, '$async.cache.eventListener.groups.$onTest') != null; + const {eventListener} = ctx.unsafe.async.Namespaces; + return Object.get(ctx, `$async.cache.${eventListener}.groups.$onTest`) != null; }); test.expect(isWrapped).toBe(true); @@ -40,7 +41,8 @@ test.describe('core/component/event', () => { // ... }); - return Object.get(ctx, '$async.cache.eventListener.groups.$onTest') != null; + const {eventListener} = ctx.unsafe.async.Namespaces; + return Object.get(ctx, `$async.cache.${eventListener}.groups.$onTest`) != null; }); test.expect(isWrapped).toBe(true); From 502e717e2ad63d257b5082ce6049578ce59fc5d7 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 29 Aug 2024 18:10:57 +0300 Subject: [PATCH 089/334] chore: updated dependencies --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index d996237d0d..27e2371aa5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5991,7 +5991,7 @@ __metadata: "@v4fire/core@github:V4Fire/Core#speedup": version: 4.0.0-alpha.43 - resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=db2d9b361576554930d51f6b088a8c325dfb3f92" + resolution: "@v4fire/core@https://github.com/V4Fire/Core.git#commit=587449c7d4cdfb70b2b09890e1def5214adb35ac" dependencies: "@babel/core": "npm:7.17.5" "@babel/helper-module-transforms": "npm:7.16.7" @@ -6134,7 +6134,7 @@ __metadata: optional: true xhr2: optional: true - checksum: 34c4df68b30106d7554fb6f35204f46d75103c48ffc5d9af0ba78bc2e9c3fc2bbe3de8a5f4eec21ac484f31b7a0eac352203cdf6a79d8eb6e1c1a1175b4e9c69 + checksum: aff8db42ef8bce993c0a0450de973852ac59adc97af149f51e7726410381b1c91991ac394d235f554f03b49568ed80589d0e2ddb9b4e2df733175131270a9037 languageName: node linkType: hard From 254f22d1106d42690d90a354d521a418ffc7f891 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 14:09:33 +0300 Subject: [PATCH 090/334] chore: fixed tests --- src/components/friends/async-render/class.ts | 4 ++++ src/components/friends/async-render/index.ts | 4 ---- src/components/friends/block/class.ts | 4 ++++ src/components/friends/block/index.ts | 4 ---- src/components/friends/daemons/class.ts | 4 ++++ src/components/friends/daemons/index.ts | 4 ---- src/components/friends/data-provider/class.ts | 4 ++++ src/components/friends/data-provider/index.ts | 4 ---- src/components/friends/dom/class.ts | 4 ++++ src/components/friends/dom/index.ts | 4 ---- src/components/friends/field/class.ts | 4 ++++ src/components/friends/field/index.ts | 4 ---- src/components/friends/module-loader/class.ts | 4 ++++ src/components/friends/module-loader/index.ts | 4 ---- src/components/friends/provide/class.ts | 4 ++++ src/components/friends/provide/index.ts | 4 ---- src/components/friends/state/class.ts | 4 ++++ src/components/friends/state/index.ts | 4 ---- src/components/friends/sync/class.ts | 4 ++++ src/components/friends/sync/index.ts | 4 ---- src/components/friends/vdom/class.ts | 4 ++++ src/components/friends/vdom/index.ts | 4 ---- 22 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/components/friends/async-render/class.ts b/src/components/friends/async-render/class.ts index a86bd3883a..6441120767 100644 --- a/src/components/friends/async-render/class.ts +++ b/src/components/friends/async-render/class.ts @@ -11,6 +11,10 @@ import Friend, { fakeMethods } from 'components/friends/friend'; import type iBlock from 'components/super/i-block/i-block'; import type { TaskOptions } from 'components/friends/async-render/api'; +//#if runtime has dummyComponents +import('components/friends/async-render/test/b-friends-async-render-dummy'); +//#endif + interface AsyncRender { waitForceRender(elementToDrop?: string | ((ctx: Friend['component']) => CanPromise>)): void; forceRender(): void; diff --git a/src/components/friends/async-render/index.ts b/src/components/friends/async-render/index.ts index 1fb83392ac..3ec4dd52f9 100644 --- a/src/components/friends/async-render/index.ts +++ b/src/components/friends/async-render/index.ts @@ -14,7 +14,3 @@ export { default } from 'components/friends/async-render/class'; export * from 'components/friends/async-render/api'; - -//#if runtime has dummyComponents -import('components/friends/async-render/test/b-friends-async-render-dummy'); -//#endif diff --git a/src/components/friends/block/class.ts b/src/components/friends/block/class.ts index 7e179de937..26abff4472 100644 --- a/src/components/friends/block/class.ts +++ b/src/components/friends/block/class.ts @@ -12,6 +12,10 @@ import type iBlock from 'components/super/i-block/i-block'; import * as block from 'components/friends/block/block'; import type { ModEventReason, ModsProp } from 'components/super/i-block/i-block'; +//#if runtime has dummyComponents +import('components/friends/block/test/b-friends-block-dummy'); +//#endif + interface Block { getMod(name: string, fromNode?: boolean): CanUndef; setMod(name: string, value: unknown, reason?: ModEventReason): boolean; diff --git a/src/components/friends/block/index.ts b/src/components/friends/block/index.ts index a0d5e59767..bdd7452257 100644 --- a/src/components/friends/block/index.ts +++ b/src/components/friends/block/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/block/class'; export * from 'components/friends/block/api'; export * from 'components/friends/block/interface'; - -//#if runtime has dummyComponents -import('components/friends/block/test/b-friends-block-dummy'); -//#endif diff --git a/src/components/friends/daemons/class.ts b/src/components/friends/daemons/class.ts index 71bf10540c..456db1a296 100644 --- a/src/components/friends/daemons/class.ts +++ b/src/components/friends/daemons/class.ts @@ -11,6 +11,10 @@ import Friend, { fakeMethods } from 'components/friends/friend'; import type iBlock from 'components/super/i-block/i-block'; import type { WrappedDaemonsDict } from 'components/friends/daemons/interface'; +//#if runtime has dummyComponents +import('components/friends/daemons/test/b-friends-daemons-dummy'); +//#endif + interface Daemons { run(this: Daemons, name: string, ...args: unknown[]): CanUndef; } diff --git a/src/components/friends/daemons/index.ts b/src/components/friends/daemons/index.ts index b39c5d9cc3..9058711e30 100644 --- a/src/components/friends/daemons/index.ts +++ b/src/components/friends/daemons/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/daemons/class'; export * from 'components/friends/daemons/api'; export * from 'components/friends/daemons/interface'; - -//#if runtime has dummyComponents -import('components/friends/daemons/test/b-friends-daemons-dummy'); -//#endif diff --git a/src/components/friends/data-provider/class.ts b/src/components/friends/data-provider/class.ts index 00c285d2e4..ecb2536cc5 100644 --- a/src/components/friends/data-provider/class.ts +++ b/src/components/friends/data-provider/class.ts @@ -19,6 +19,10 @@ import type iDataProvider from 'components/traits/i-data-provider/i-data-provide import type { DataProviderProp, DataProviderOptions, DefaultRequest } from 'components/friends/data-provider/interface'; +//#if runtime has dummyComponents +import('components/friends/data-provider/test/provider'); +//#endif + interface DataProvider { url(): CanUndef; url(value: string): this; diff --git a/src/components/friends/data-provider/index.ts b/src/components/friends/data-provider/index.ts index db5f47df72..a1fbbcbef9 100644 --- a/src/components/friends/data-provider/index.ts +++ b/src/components/friends/data-provider/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/data-provider/class'; export * from 'components/friends/data-provider/api'; export * from 'components/friends/data-provider/interface'; - -//#if runtime has dummyComponents -import('components/friends/data-provider/test/provider'); -//#endif diff --git a/src/components/friends/dom/class.ts b/src/components/friends/dom/class.ts index 7d5c0c879d..5f430e553e 100644 --- a/src/components/friends/dom/class.ts +++ b/src/components/friends/dom/class.ts @@ -18,6 +18,10 @@ import type { ComponentElement } from 'components/super/i-block/i-block'; import type { DOMModificationOptions, ElCb } from 'components/friends/dom/interface'; +//#if runtime has dummyComponents +import('components/friends/dom/test/b-friends-dom-dummy'); +//#endif + interface DOM { delegate(selector: string, fn: T): T; delegateElement(name: string, fn: T): T; diff --git a/src/components/friends/dom/index.ts b/src/components/friends/dom/index.ts index b5cfa3a697..25af88b8d8 100644 --- a/src/components/friends/dom/index.ts +++ b/src/components/friends/dom/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/dom/class'; export * from 'components/friends/dom/api'; export * from 'components/friends/dom/interface'; - -//#if runtime has dummyComponents -import('components/friends/dom/test/b-friends-dom-dummy'); -//#endif diff --git a/src/components/friends/field/class.ts b/src/components/friends/field/class.ts index abbfd71523..d2bb0fdf33 100644 --- a/src/components/friends/field/class.ts +++ b/src/components/friends/field/class.ts @@ -14,6 +14,10 @@ import { setField } from 'components/friends/field/set'; import type { KeyGetter, ValueGetter } from 'components/friends/field/interface'; +//#if runtime has dummyComponents +import('components/friends/field/test/b-friends-field-dummy'); +//#endif + interface Field { get(path: string, getter: ValueGetter): CanUndef; get(path: string, obj?: Nullable, getter?: ValueGetter): CanUndef; diff --git a/src/components/friends/field/index.ts b/src/components/friends/field/index.ts index 45cfb8e3da..6ac37ca440 100644 --- a/src/components/friends/field/index.ts +++ b/src/components/friends/field/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/field/class'; export * from 'components/friends/field/api'; export * from 'components/friends/field/interface'; - -//#if runtime has dummyComponents -import('components/friends/field/test/b-friends-field-dummy'); -//#endif diff --git a/src/components/friends/module-loader/class.ts b/src/components/friends/module-loader/class.ts index 127b20dcbf..5fb2e8d932 100644 --- a/src/components/friends/module-loader/class.ts +++ b/src/components/friends/module-loader/class.ts @@ -9,6 +9,10 @@ import Friend, { fakeMethods } from 'components/friends/friend'; import type { Module, Signal } from 'components/friends/module-loader/interface'; +//#if runtime has dummyComponents +import('components/friends/module-loader/test/b-friends-module-loader-dummy'); +//#endif + interface ModuleLoader { load(...modules: Module[]): CanPromise>; loadBucket(bucketName: string, ...modules: Module[]): number; diff --git a/src/components/friends/module-loader/index.ts b/src/components/friends/module-loader/index.ts index c9d34fd4a5..d19980533c 100644 --- a/src/components/friends/module-loader/index.ts +++ b/src/components/friends/module-loader/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/module-loader/class'; export * from 'components/friends/module-loader/api'; export * from 'components/friends/module-loader/interface'; - -//#if runtime has dummyComponents -import('components/friends/module-loader/test/b-friends-module-loader-dummy'); -//#endif diff --git a/src/components/friends/provide/class.ts b/src/components/friends/provide/class.ts index 0c0753b130..92ba567b01 100644 --- a/src/components/friends/provide/class.ts +++ b/src/components/friends/provide/class.ts @@ -15,6 +15,10 @@ import * as api from 'components/friends/provide/api'; import type { Mods } from 'components/friends/provide/interface'; +//#if runtime has dummyComponents +import('components/friends/provide/test/b-friends-provide-dummy'); +//#endif + interface Provide { fullComponentName(): string; // eslint-disable-next-line @typescript-eslint/unified-signatures diff --git a/src/components/friends/provide/index.ts b/src/components/friends/provide/index.ts index 3721041685..1f2b083b64 100644 --- a/src/components/friends/provide/index.ts +++ b/src/components/friends/provide/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/provide/class'; export * from 'components/friends/provide/api'; export * from 'components/friends/provide/interface'; - -//#if runtime has dummyComponents -import('components/friends/provide/test/b-friends-provide-dummy'); -//#endif diff --git a/src/components/friends/state/class.ts b/src/components/friends/state/class.ts index 201088d8d1..33af4723e8 100644 --- a/src/components/friends/state/class.ts +++ b/src/components/friends/state/class.ts @@ -9,6 +9,10 @@ import Friend, { fakeMethods } from 'components/friends/friend'; import iBlock from 'components/super/i-block/i-block'; +//#if runtime has dummyComponents +import('components/friends/state/test/b-friends-state-dummy'); +//#endif + let baseSyncRouterState; diff --git a/src/components/friends/state/index.ts b/src/components/friends/state/index.ts index d40129fc0c..2fbf422f75 100644 --- a/src/components/friends/state/index.ts +++ b/src/components/friends/state/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/state/class'; export * from 'components/friends/state/api'; export * from 'components/friends/state/interface'; - -//#if runtime has dummyComponents -import('components/friends/state/test/b-friends-state-dummy'); -//#endif diff --git a/src/components/friends/sync/class.ts b/src/components/friends/sync/class.ts index 504d3248cd..ec7e15d9fd 100644 --- a/src/components/friends/sync/class.ts +++ b/src/components/friends/sync/class.ts @@ -26,6 +26,10 @@ import type { } from 'components/friends/sync/interface'; +//#if runtime has dummyComponents +import('components/friends/sync/test/b-friends-sync-dummy'); +//#endif + interface Sync { mod( modName: string, diff --git a/src/components/friends/sync/index.ts b/src/components/friends/sync/index.ts index c5d73729b7..4cf7acf03d 100644 --- a/src/components/friends/sync/index.ts +++ b/src/components/friends/sync/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/sync/class'; export * from 'components/friends/sync/api'; export * from 'components/friends/sync/interface'; - -//#if runtime has dummyComponents -import('components/friends/sync/test/b-friends-sync-dummy'); -//#endif diff --git a/src/components/friends/vdom/class.ts b/src/components/friends/vdom/class.ts index c9f7989c93..035a45fddf 100644 --- a/src/components/friends/vdom/class.ts +++ b/src/components/friends/vdom/class.ts @@ -14,6 +14,10 @@ import type { RenderFactory, RenderFn, ComponentInterface } from 'components/sup import type { VNodeDescriptor, VNodeOptions } from 'components/friends/vdom/interface'; +//#if runtime has dummyComponents +import('components/friends/vdom/test/b-friends-vdom-dummy'); +//#endif + interface VDOM { closest(component: string | ClassConstructor | Function): CanNull; findElement(name: string, where: VNode, ctx?: iBlock): CanNull; diff --git a/src/components/friends/vdom/index.ts b/src/components/friends/vdom/index.ts index 8749219ab7..242ff9b02d 100644 --- a/src/components/friends/vdom/index.ts +++ b/src/components/friends/vdom/index.ts @@ -15,7 +15,3 @@ export { default } from 'components/friends/vdom/class'; export * from 'components/friends/vdom/api'; export * from 'components/friends/vdom/interface'; - -//#if runtime has dummyComponents -import('components/friends/vdom/test/b-friends-vdom-dummy'); -//#endif From 51b9017c832044458fb24d29a66a5052084c33d7 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 14:48:10 +0300 Subject: [PATCH 091/334] fix: fixes after refactoring --- src/core/component/init/states/created.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/init/states/created.ts b/src/core/component/init/states/created.ts index 533eedad58..4610215cde 100644 --- a/src/core/component/init/states/created.ts +++ b/src/core/component/init/states/created.ts @@ -33,7 +33,7 @@ export function createdState(component: ComponentInterface): void { if (parent != null) { const isRegularComponent = unsafe.meta.params.functional !== true, - isDynamicallyMountedComponent = '$remoteParent' in unsafe.r; + isDynamicallyMountedComponent = '$remoteParent' in unsafe.$root; const destroy = (opts: Required) => { // A component might have already been removed by explicitly calling $destroy From ad3cbdadf6e81b6297f2d46eb4ea65e3a60e320b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 15:10:17 +0300 Subject: [PATCH 092/334] chore: fixes TS --- src/components/base/b-router/b-router.ts | 3 +-- src/core/dom/intersection-watcher/test/unit/options.ts | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/base/b-router/b-router.ts b/src/components/base/b-router/b-router.ts index 0020c2a9ce..9d088ecc2e 100644 --- a/src/components/base/b-router/b-router.ts +++ b/src/components/base/b-router/b-router.ts @@ -12,7 +12,6 @@ */ import symbolGenerator from 'core/symbol'; -import type Async from 'core/async'; import globalRoutes from 'routes'; import * as router from 'core/router'; @@ -53,7 +52,7 @@ const @component() export default class bRouter extends iRouterProps { /** @inheritDoc */ - declare public async: Async; + declare public async: iRouterProps['async']; /** * Compiled application route map diff --git a/src/core/dom/intersection-watcher/test/unit/options.ts b/src/core/dom/intersection-watcher/test/unit/options.ts index 6a751df27c..ee08fc0c81 100644 --- a/src/core/dom/intersection-watcher/test/unit/options.ts +++ b/src/core/dom/intersection-watcher/test/unit/options.ts @@ -30,14 +30,16 @@ test.describe('core/dom/intersection-watcher: watching for the intersection with IntersectionObserverModule: JSHandle<{default: typeof IntersectionObserver}>, HeightmapObserverModule: JSHandle<{default: typeof HeightmapObserver}>, - intersectionObserver: JSHandle, - heightmapObserver: JSHandle; + intersectionObserver: JSHandle, + heightmapObserver: JSHandle; let target: ElementHandle, wasInvoked: JSHandle<{flag: boolean}>; - const getObserver = (engine: string) => engine === 'intersection' ? intersectionObserver : heightmapObserver; + function getObserver(engine: string): JSHandle { + return Object.cast(engine === 'intersection' ? intersectionObserver : heightmapObserver); + } test.beforeEach(async ({demoPage, page}) => { await demoPage.goto(); From 132d5302ccf2d5d8460af363efa1fa531b0c4179 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 15:17:19 +0300 Subject: [PATCH 093/334] chore: fixes TS --- src/core/dom/intersection-watcher/test/unit/root.ts | 8 +++++--- src/core/dom/intersection-watcher/test/unit/unwatch.ts | 8 +++++--- src/core/dom/intersection-watcher/test/unit/watch.ts | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/core/dom/intersection-watcher/test/unit/root.ts b/src/core/dom/intersection-watcher/test/unit/root.ts index 06ffbd2c2f..1c9426edfd 100644 --- a/src/core/dom/intersection-watcher/test/unit/root.ts +++ b/src/core/dom/intersection-watcher/test/unit/root.ts @@ -30,15 +30,17 @@ test.describe('core/dom/intersection-watcher: watching for the intersection with IntersectionObserverModule: JSHandle<{default: typeof IntersectionObserver}>, HeightmapObserverModule: JSHandle<{default: typeof HeightmapObserver}>, - intersectionObserver: JSHandle, - heightmapObserver: JSHandle; + intersectionObserver: JSHandle, + heightmapObserver: JSHandle; let root: ElementHandle, rootInner: ElementHandle, target: ElementHandle; - const getObserver = (engine: string) => engine === 'intersection' ? intersectionObserver : heightmapObserver; + function getObserver(engine: string): JSHandle { + return Object.cast(engine === 'intersection' ? intersectionObserver : heightmapObserver); + } test.beforeEach(async ({demoPage, page}) => { await demoPage.goto(); diff --git a/src/core/dom/intersection-watcher/test/unit/unwatch.ts b/src/core/dom/intersection-watcher/test/unit/unwatch.ts index c2c5bcb8e2..771d377ea0 100644 --- a/src/core/dom/intersection-watcher/test/unit/unwatch.ts +++ b/src/core/dom/intersection-watcher/test/unit/unwatch.ts @@ -30,14 +30,16 @@ test.describe('core/dom/intersection-watcher: cancelling watching for the inters IntersectionObserverModule: JSHandle<{default: typeof IntersectionObserver}>, HeightmapObserverModule: JSHandle<{default: typeof HeightmapObserver}>, - intersectionObserver: JSHandle, - heightmapObserver: JSHandle; + intersectionObserver: JSHandle, + heightmapObserver: JSHandle; let target: ElementHandle, wasInvoked: JSHandle<{flag: boolean}>; - const getObserver = (engine: string) => engine === 'intersection' ? intersectionObserver : heightmapObserver; + function getObserver(engine: string): JSHandle { + return Object.cast(engine === 'intersection' ? intersectionObserver : heightmapObserver); + } test.beforeEach(async ({demoPage, page}) => { await demoPage.goto(); diff --git a/src/core/dom/intersection-watcher/test/unit/watch.ts b/src/core/dom/intersection-watcher/test/unit/watch.ts index 5e7084ca97..6896d9f217 100644 --- a/src/core/dom/intersection-watcher/test/unit/watch.ts +++ b/src/core/dom/intersection-watcher/test/unit/watch.ts @@ -30,13 +30,15 @@ test.describe('core/dom/intersection-watcher: watching for the intersection of a IntersectionObserverModule: JSHandle<{default: typeof IntersectionObserver}>, HeightmapObserverModule: JSHandle<{default: typeof HeightmapObserver}>, - intersectionObserver: JSHandle, - heightmapObserver: JSHandle; + intersectionObserver: JSHandle, + heightmapObserver: JSHandle; let target: ElementHandle; - const getObserver = (engine: string) => engine === 'intersection' ? intersectionObserver : heightmapObserver; + function getObserver(engine: string): JSHandle { + return Object.cast(engine === 'intersection' ? intersectionObserver : heightmapObserver); + } test.beforeEach(async ({demoPage, page}) => { await demoPage.goto(); From b3f33e85509d4fd85a2e6b82848daaaf09a4d4aa Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 15:20:16 +0300 Subject: [PATCH 094/334] chore: fixed import --- src/core/theme-manager/system-theme-extractor/interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/theme-manager/system-theme-extractor/interface.ts b/src/core/theme-manager/system-theme-extractor/interface.ts index e1f9fa1830..edad9aa21d 100644 --- a/src/core/theme-manager/system-theme-extractor/interface.ts +++ b/src/core/theme-manager/system-theme-extractor/interface.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type { AsyncOptions, ClearOptions } from 'core/async/modules/base/interface'; +import type { AsyncOptions, ClearOptions } from 'core/async'; /** * An API for retrieving and monitoring the system's visual appearance theme From c6308c025c815e8429192786690d3509c3105d5a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 16:38:38 +0300 Subject: [PATCH 095/334] docs: improved doc --- .../component/decorators/component/README.md | 2 +- .../component/decorators/computed/README.md | 83 ++++++++++++++++--- src/core/component/decorators/field/README.md | 22 ++--- src/core/component/decorators/hook/README.md | 31 ++++--- .../decorators/interface/accessor.ts | 17 +++- src/core/component/decorators/prop/README.md | 6 +- .../component/decorators/system/README.md | 20 ++--- src/core/component/decorators/watch/README.md | 23 ++++- src/core/component/decorators/watch/index.ts | 2 +- src/core/component/interface/watch.ts | 18 ++-- 10 files changed, 159 insertions(+), 65 deletions(-) diff --git a/src/core/component/decorators/component/README.md b/src/core/component/decorators/component/README.md index af71ee0e46..d9138bd812 100644 --- a/src/core/component/decorators/component/README.md +++ b/src/core/component/decorators/component/README.md @@ -15,7 +15,7 @@ export default class bUser extends iBlock { } ``` -## How to create a component? +## Declaring a Component To register a new component, generate a simple JS/TS class and apply the `@component` decorator to it. You can also pass additional parameters to this decorator. diff --git a/src/core/component/decorators/computed/README.md b/src/core/component/decorators/computed/README.md index 31d8dee536..4905a088de 100644 --- a/src/core/component/decorators/computed/README.md +++ b/src/core/component/decorators/computed/README.md @@ -17,13 +17,13 @@ export default class bUser extends iBlock { stageProp?: 'basic' | 'advanced'; // This is a cacheable computed field with feature of watching and cache invalidation - @computed({cache: true, dependencies: ['fName', 'lName']}) + @computed({dependencies: ['fName', 'lName']}) get fullName() { return `${this.fName} ${this.lName}`; } // This is a cacheable computed field without cache invalidation - @computed({cache: true}) + @computed({cache: 'forever'}) get id() { return Math.random(); } @@ -40,27 +40,76 @@ export default class bUser extends iBlock { } ``` -## What differences between accessors and computed fields? +## What Are the Differences Between Accessors and Computed Fields? A computed field is an accessor that can have its value cached or be watched for changes. To enable value caching, you can use the `@computed` decorator when defining or overriding your accessor. Once you add the decorator, the first time the getter value is accessed, it will be cached. +In essence, computed fields provide an elegant and efficient way to derive values from a component's data and state, +making it easier to manage and update dynamic UI states based on changes in other components or the application's data. + +### Cache Invalidation + To support cache invalidation or add change watching capabilities, -you can provide a list of your accessor dependencies or use the `cache = 'auto'` option. +you can provide a list of your accessor dependencies or use the `cache: 'auto'` option. By specifying dependencies, the computed field will automatically update when any of its dependencies change, whereas the `cache = 'auto'` option will invalidate the cache when it detects a change in any of the dependencies. -In essence, computed fields provide an elegant and efficient way to derive values from a component's data and state, -making it easier to manage and update dynamic UI states based on changes in other components or the application's data. +#### `cache: 'auto'` vs `dependencies` + +The main difference between these two methods is that when observing a @computed field with `cache: 'auto'`, +it is necessary to execute the instructions inside the getter. +If the getter contains resource-intensive operations, this can have performance implications. -## Additional options +When explicitly defining dependencies, and if the field has an explicit connection by name +(using the naming convention "${property} → ${property}Prop | ${property}Store"), +it is not necessary to execute the getter to initialize observation. + +### Effect Propagation + +Even though the value of a @computed field can be cached, +there is still a need for the "effect" of this field to propagate to the template. +Put, to maintain a reactive connection between the values used inside and the template, +it is necessary that when such a field is called within the template, it is not taken from the cache, but live. +Therefore, cached @computed fields do not use the cache until the component's template has been rendered at least once. + +#### Eternal Caching + +Sometimes it is necessary for the value of a @computed field to be cached upon first use, +but then always retrieved from the cache thereafter. +Consequently, such a getter is not suitable for scenarios where it may contain side effects or need to be observed. +However, it is well-suited for situations where a certain value needs to be initialized lazily +and then should never change. +For instance, this is how the initialization of friendly classes in iBlock is done. + +To activate such a caching mode, it is necessary to set the value of the `cache` parameter to `'forever'`. + +```typescript +import iBlock, { component, field, computed } from 'components/super/i-block/i-block'; + +@component() +class bExample extends iBlock { + // This getter will be executed only once during the first touch + @computed({cache: 'forever'}) + get exampleElements(): number { + return document.querySelectorAll('.example'); + } +} +``` + +## Additional Options ### [cache] If set to true, the accessor value will be cached after the first touch. The option is set to true by default if it also provided `dependencies` or the bound accessor matches by the name with another prop or field. + +Note that to support the propagation of the getter's effect to the template, +caching never works until the component has been rendered for the first time. +If you want to ensure that the cached value is never invalidated, you should set the parameter to `'forever'`. + If the option value is passed as `auto` caching will be delegated to the used component library. ```typescript @@ -69,7 +118,7 @@ import iBlock, { component, field, computed } from 'components/super/i-block/i-b @component() class bExample extends iBlock { // The value is cached after the first touch and will never be reset - @computed({cache: true}) + @computed({cache: 'forever'}) get hashCode(): number { return Math.random(); } @@ -77,16 +126,25 @@ class bExample extends iBlock { @field() i: number = 0; - // The value is cached after the first touch, but the cache can be reset if the fields used internally change + // The value is cached after the first touch, but the cache can be reset if the fields used internally change. + // The caching logic in this mode is handled by the library being used, such as Vue. @computed({cache: 'auto'}) get iWrapper(): number { return this.i; } + + // The value is cached after the first touch, but the cache can be reset if the fields used internally change. + // The caching logic in this case is carried out using the V4Fire library. + @computed({dependencies: ['i']}) + get iWrapper2(): number { + return this.i; + } } ``` Also, when an accessor has a logically related prop/field -(using the naming convention "${property} -> ${property}Prop | ${property}Store") we don't need to add additional dependencies. +(using the naming convention "${property} → ${property}Prop | ${property}Store") +we don't need to add additional dependencies. ```typescript import iBlock, { component, prop, field, computed } from 'components/super/i-block/i-block'; @@ -149,7 +207,7 @@ class bExample extends iBlock { ### [dependencies] A list of dependencies for the accessor. -The dependencies are needed to watch for the accessor mutations or to invalidate its cache. +The dependencies are necessary to watch for the accessor mutations or to invalidate its cache. ```typescript import iBlock, {component, field, computed} from 'components/super/i-block/i-block'; @@ -167,7 +225,8 @@ class bExample extends iBlock { ``` Also, when an accessor has a logically related prop/field -(using the naming convention "${property} -> ${property}Prop | ${property}Store") we don't need to add additional dependencies. +(using the naming convention "${property} → ${property}Prop | ${property}Store") +we don't need to add additional dependencies. ```typescript import iBlock, { component, prop, field, computed } from 'components/super/i-block/i-block'; diff --git a/src/core/component/decorators/field/README.md b/src/core/component/decorators/field/README.md index 998f83b343..7bc30a337d 100644 --- a/src/core/component/decorators/field/README.md +++ b/src/core/component/decorators/field/README.md @@ -22,7 +22,7 @@ class bExample extends iBlock { } ``` -## What differences between fields and system fields? +## What Are the Differences Between Fields and System Fields? The main difference between fields and system fields in V4Fire is that any changes to a regular field can trigger a re-render of the component template. @@ -35,7 +35,7 @@ Regular fields are initialized in the `created` hook, while system fields are in By understanding the differences between regular fields and system fields, developers can design and optimize their components for optimal performance and behavior. -## Field initialization order +## Field Initialization Order Because the `init` function takes a reference to the field's store as the second argument, then we can generate field values from other fields. @@ -84,7 +84,7 @@ Now, the code will work as expected. The `after` parameter specifies that property `a` should be initialized before property `b`, ensuring that the value of `a` is available when calculating the value of `b`. -### Atomic properties +### Atomic Properties When there are properties that are required for most other properties, it can become quite tedious to manually write the after parameter in each place. @@ -118,7 +118,7 @@ Dependency on non-atoms can result in a deadlock. Using the atom parameter is a convenient way to guarantee the initialization order for properties that are required by most other properties, reducing the need for explicit after declarations. -## Initialization loop and asynchronous operations +## Initialization Loop and Asynchronous Operations It is important to note that all component properties are initialized synchronously, meaning you cannot return a Promise from a property initializer and expect the component to wait for it to resolve @@ -130,7 +130,7 @@ Technically, a property initializer can return both a Promise and not return any In such cases, you can later change the value of the property. While the data is being loaded asynchronously, -the component can display a loading indicator or handle the situation in a suitable manner. +the component can display a loading indicator or handle the situation suitably. This approach is considered idiomatic and does not have any unpredictable consequences. Here's an example code snippet that demonstrates this behavior: @@ -156,10 +156,10 @@ It is important to note that when working with asynchronous operations during in it is recommended to use the `field.set` method or directly modify the component instance (the first argument of the initializer function) to update the value of the property. -By utilizing this approach, you can handle asynchronous data loading and modify property values once +By using this approach, you can handle asynchronous data loading and modify property values once the data is available without negatively impacting the performance of the component. -## Additional options +## Additional Options ### [unique = `false`] @@ -246,7 +246,7 @@ class bExample extends iBlock { ### [forceUpdate = `false`] If set to true, property changes will cause the template to be guaranteed to be re-rendered. -Be aware that enabling this property may result in redundant redraws. +Be aware that enabling this property may result in redundant redrawing. ### [after] @@ -273,7 +273,7 @@ class bExample extends iBlock { ### [atom = `false`] This option indicates that property should be initialized before all non-atom properties. -It is needed when you have a field that must be guaranteed to be initialized before other fields, +It is necessary when you have a field that must be guaranteed to be initialized before other fields, and you don't want to use `after` everywhere. But you can still use `after` along with other atomic fields. @@ -312,7 +312,7 @@ class bExample extends iBlock { // Also, see core/object/watch { - // If set to false, then a handler that is invoked on the watcher event does not take any arguments from the event + // If set to false, then a handler invoked on the watcher event does not take any arguments from the event provideArgs: false, // How the event handler should be called: @@ -350,7 +350,7 @@ as regular or functional. This option is only relevant for functional components. The fact is that when a component state changes, all its child functional components are recreated from scratch. But we need to restore the state of such components. By default, properties are simply copied from old instances to -new ones, but sometimes this strategy does not suit us. This option helps here - it allows you to declare that +new ones, but sometimes this strategy does not suit us. This option helps here—it allows you to declare that a certain property should be mixed based on the old and new values. Set this property to true to enable the strategy of merging old and new values. diff --git a/src/core/component/decorators/hook/README.md b/src/core/component/decorators/hook/README.md index cc9a80dcbc..561073c548 100644 --- a/src/core/component/decorators/hook/README.md +++ b/src/core/component/decorators/hook/README.md @@ -29,7 +29,7 @@ class bExample extends iBlock { } ``` -## Component life cycle +## Component Life Cycle V4Fire components have a standard life cycle: the component is created, the component is mounted to the DOM, the component is unmounted from the DOM, and so on. @@ -48,8 +48,9 @@ Also, V4Fire uses the `beforeDestroy` and `destroyed` hooks, not `beforeUnmount` The need for this hook exists due to Vue limitations: the fact is that when a component is called within a template, it has a state when it does not yet have its own methods and fields, but only props (`beforeCreate`). After `beforeCreate`, a special function is called on the component, which forms a base object with the watchable fields -of the component, and only then `created` is triggered. So, before `created` we cannot use the component API, like methods, -getters, etc. However, in order to use some methods before the `created` hook, the [[iBlock]] class has the following code. +of the component, and only then `created` is triggered. +So, before `created` we cannot use the component API, like methods, getters, etc. +However, to use some methods before the `created` hook, the [[iBlock]] class has the following code. ``` @hook('beforeRuntime') @@ -68,16 +69,19 @@ protected initBaseAPI() { ``` That is, before `beforeCreate`, a special method is triggered that explicitly sets the most necessary API, -which the component should always have. There are not many methods that can be used before the `created` hook, -and usually all of them are registered in `iBlock.initBaseAPI`. However, if your component has a new method that needs -to be used in this way, you can always override the `initBaseAPI` method. +which the component should always have. +There are few methods that can be used before the `created` hook, +and usually all of them are registered in `iBlock.initBaseAPI`. +However, if your component has a new method that needs to be used in this way, +you can always override the `initBaseAPI` method. ### beforeDataCreate It is often necessary to make some modification to watchable fields (such as normalization) before creating a component, because once created, any change to such fields can cause re-rendering and can be disastrous for performance. We have links, initializers, and API to control the order of initialization, but what if we need to get the entire -watchable store and modify it in a complex way. It is to solve this problem that the `beforeDataCreate` hook exists: +watchable store and modify it complexly. +It is to solve this problem that the `beforeDataCreate` hook exists: it will be called exactly when all observable properties have been created, but not yet linked to the component, i.e., we can safely change them and not expect consequences. @@ -104,12 +108,12 @@ export default class bExample extends iBlock { ``` It should also be noted that the `@prop` and `@system` properties are initialized before `beforeCreate`, -so no special methods or hooks are needed to access them. +so no special methods or hooks are necessary to access them. As a rule, it is better to use link mechanisms to create relationships during initialization and normalization, but nevertheless, `beforeDataCreate` can be quite useful. -## Hook change events +## Hook Change Events Whenever a component hook value changes, the component emits a series of events that can be listened to both inside and outside the component. @@ -177,7 +181,7 @@ To bind a method to a specific hook, there are three ways: } ``` -### Component hook accessor +### Component Hook Accessor All V4Fire components have a hook accessor that indicates which hook the component is currently in. @@ -194,11 +198,12 @@ class bExample extends iBlock { } ``` -## Hook handler execution order +## Hook Handler Execution Order All hook handlers are executed in a queue: those added through the decorator are executed first (in order of addition), -and then the associated methods (if any) are already executed. If we need to declare that some method should be executed -only after the execution of another, then we can set this explicitly through a decorator. +and then the associated methods (if any) are already executed. +If we need to declare that some method should be executed only after the execution of another, +then we can set this explicitly through a decorator. ```typescript import iBlock, { component, field, hook } from 'components/super/i-block/i-block'; diff --git a/src/core/component/decorators/interface/accessor.ts b/src/core/component/decorators/interface/accessor.ts index 18e1343687..0d332d59c2 100644 --- a/src/core/component/decorators/interface/accessor.ts +++ b/src/core/component/decorators/interface/accessor.ts @@ -13,8 +13,9 @@ export interface DecoratorComponentAccessor extends DecoratorFunctionalOptions { /** * If set to true, the accessor value will be cached after the first touch. * - * The option is set to true by default if it also provided `dependencies` or the bound accessor matches - * by the name with another prop or field. + * Note that to support the propagation of the getter's effect to the template, + * caching never works until the component has been rendered for the first time. + * If you want to ensure that the cached value is never invalidated, you should set the parameter to `'forever'`. * * If the option value is passed as `auto` caching will be delegated to the used component library. * @@ -28,7 +29,7 @@ export interface DecoratorComponentAccessor extends DecoratorFunctionalOptions { * @component() * class bExample extends iBlock { * // The value is cached after the first touch and will never be reset - * @computed({cache: true}) + * @computed({cache: 'forever'}) * get hashCode(): number { * return Math.random(); * } @@ -36,11 +37,19 @@ export interface DecoratorComponentAccessor extends DecoratorFunctionalOptions { * @field() * i: number = 0; * - * // The value is cached after the first touch, but the cache can be reset if the fields used internally change + * // The value is cached after the first touch, but the cache can be reset if the fields used internally change. + * // The caching logic in this mode is handled by the library being used, such as Vue. * @computed({cache: 'auto'}) * get iWrapper(): number { * return this.i; * } + * + * // The value is cached after the first touch, but the cache can be reset if the fields used internally change. + * // The caching logic in this case is carried out using the V4Fire library. + * @computed({dependencies: ['i']}) + * get iWrapper2(): number { + * return this.i; + * } * } * ``` */ diff --git a/src/core/component/decorators/prop/README.md b/src/core/component/decorators/prop/README.md index d62ee0602e..ebcd2f46ae 100644 --- a/src/core/component/decorators/prop/README.md +++ b/src/core/component/decorators/prop/README.md @@ -39,7 +39,7 @@ class bExample extends iBlock { } ``` -## Naming conventions and linking fields with props +## Naming Conventions and Linking Fields with Props As mentioned earlier, component props cannot be changed from within the component. However, very often there is a need to violate this rule. @@ -154,7 +154,7 @@ And calling our component from another template will be like this. As you can see, we got rid of unnecessary boilerplate code and the need to remember the name of the component prop. -## Additional options +## Additional Options ### [type] @@ -284,7 +284,7 @@ Keep in mind that if you use this option, you must ensure that the prop is never implicitly in the template. For instance, consider a situation where you have a field bound to such a prop, -and it is used in the template in conjunction with v-model. +and it is used in the template in conjunction with `v-model`. This will lead to incorrect behavior (updating the prop will not lead to updating the value in the input). ```typescript diff --git a/src/core/component/decorators/system/README.md b/src/core/component/decorators/system/README.md index 04b66cbb6e..23bfd6699a 100644 --- a/src/core/component/decorators/system/README.md +++ b/src/core/component/decorators/system/README.md @@ -22,7 +22,7 @@ class bExample extends iBlock { } ``` -## What differences between fields and system fields? +## What Are the Differences Between Fields and System Fields? The main difference between fields and system fields in V4Fire is that any changes to a regular field can trigger a re-render of the component template. @@ -35,7 +35,7 @@ Regular fields are initialized in the `created` hook, while system fields are in By understanding the differences between regular fields and system fields, developers can design and optimize their components for optimal performance and behavior. -## Field initialization order +## Field Initialization Order Because the `init` function takes a reference to the field's store as the second argument, then we can generate field values from other fields. @@ -84,7 +84,7 @@ Now, the code will work as expected. The `after` parameter specifies that property `a` should be initialized before property `b`, ensuring that the value of `a` is available when calculating the value of `b`. -### Atomic properties +### Atomic Properties When there are properties that are required for most other properties, it can become quite tedious to manually write the after parameter in each place. @@ -118,7 +118,7 @@ Dependency on non-atoms can result in a deadlock. Using the atom parameter is a convenient way to guarantee the initialization order for properties that are required by most other properties, reducing the need for explicit after declarations. -## Initialization loop and asynchronous operations +## Initialization Loop and Asynchronous Operations It is important to note that all component properties are initialized synchronously, meaning you cannot return a Promise from a property initializer and expect the component to wait for it to resolve @@ -130,7 +130,7 @@ Technically, a property initializer can return both a Promise and not return any In such cases, you can later change the value of the property. While the data is being loaded asynchronously, -the component can display a loading indicator or handle the situation in a suitable manner. +the component can display a loading indicator or handle the situation suitably. This approach is considered idiomatic and does not have any unpredictable consequences. Here's an example code snippet that demonstrates this behavior: @@ -156,10 +156,10 @@ It is important to note that when working with asynchronous operations during in it is recommended to use the `field.set` method or directly modify the component instance (the first argument of the initializer function) to update the value of the property. -By utilizing this approach, you can handle asynchronous data loading and modify property values once +By using this approach, you can handle asynchronous data loading and modify property values once the data is available without negatively impacting the performance of the component. -#### Additional options +#### Additional Options ### [unique = `false`] @@ -268,7 +268,7 @@ class bExample extends iBlock { ### [atom = `false`] This option indicates that property should be initialized before all non-atom properties. -It is needed when you have a field that must be guaranteed to be initialized before other fields, +It is necessary when you have a field that must be guaranteed to be initialized before other fields, and you don't want to use `after` everywhere. But you can still use `after` along with other atomic fields. @@ -307,7 +307,7 @@ class bExample extends iBlock { // Also, see core/object/watch { - // If set to false, then a handler that is invoked on the watcher event does not take any arguments from the event + // If set to false, then a handler invoked on the watcher event does not take any arguments from the event provideArgs: false, // How the event handler should be called: @@ -345,7 +345,7 @@ as regular or functional. This option is only relevant for functional components. The fact is that when a component state changes, all its child functional components are recreated from scratch. But we need to restore the state of such components. By default, properties are simply copied from old instances to -new ones, but sometimes this strategy does not suit us. This option helps here - it allows you to declare that +new ones, but sometimes this strategy does not suit us. This option helps here—it allows you to declare that a certain property should be mixed based on the old and new values. Set this property to true to enable the strategy of merging old and new values. diff --git a/src/core/component/decorators/watch/README.md b/src/core/component/decorators/watch/README.md index 37a5e68f11..29ed426f8f 100644 --- a/src/core/component/decorators/watch/README.md +++ b/src/core/component/decorators/watch/README.md @@ -52,7 +52,7 @@ class Foo extends iBlock { } ``` -In order to listen to an event, you should use the special delimiter `:` within a watch path. +To listen to an event, you should use the special delimiter `:` within a watch path. You can also specify an event emitter to listen to by writing a link before `:`. Here are some examples: @@ -188,3 +188,24 @@ Determines the execution timing of the event handler: and is assured to be called before all linked templates are updated. 3. `sync` - the handler will be invoked immediately after each mutation. + +### [test] + +A function to determine whether a watcher should be initialized or not. +If the function returns false, the watcher will not be initialized. +Useful for precise component optimizations. + +```typescript +import iBlock, { component, prop, watch } from 'components/super/i-block/i-block'; + +@component() +export default class bExample extends iBlock { + @prop({required: false}) + params?: Dictionary; + + @watch({path: 'params', test: (ctx) => ctx.params != null}) + watcher() { + + } +} +``` diff --git a/src/core/component/decorators/watch/index.ts b/src/core/component/decorators/watch/index.ts index 54734f3f95..43c696941a 100644 --- a/src/core/component/decorators/watch/index.ts +++ b/src/core/component/decorators/watch/index.ts @@ -62,7 +62,7 @@ import type { DecoratorFieldWatcher, DecoratorMethodWatcher } from 'core/compone * } * ``` * - * In order to listen to an event, you should use the special delimiter `:` within a watch path. + * To listen to an event, you should use the special delimiter `:` within a watch path. * You can also specify an event emitter to listen to by writing a link before `:`. * Here are some examples: * diff --git a/src/core/component/interface/watch.ts b/src/core/component/interface/watch.ts index a149b16cce..4da402bd7e 100644 --- a/src/core/component/interface/watch.ts +++ b/src/core/component/interface/watch.ts @@ -183,6 +183,15 @@ export interface MethodWatcher< */ functional?: boolean; + /** + * A function to determine whether a watcher should be initialized or not. + * If the function returns false, the watcher will not be initialized. + * Useful for precise component optimizations. + * + * @param ctx + */ + test?(ctx: CTX): boolean; + /** * An object with additional settings for the event emitter */ @@ -220,15 +229,6 @@ export interface MethodWatcher< * ``` */ wrapper?: WatchWrapper; - - /** - * A function to determine whether a watcher should be initialized or not. - * If the function returns false, the watcher will not be initialized. - * Useful for precise component optimizations. - * - * @param ctx - */ - test?(ctx: CTX): boolean; } export type WatchPath = From 83420240b9e0bb8257b4f9a902458a192c76875d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 16:49:35 +0300 Subject: [PATCH 096/334] chore: stylish fixes --- src/core/component/event/emitter.ts | 3 +-- src/core/component/field/README.md | 2 +- src/core/component/render/daemon/index.ts | 6 ++---- src/core/component/traverse/index.ts | 3 +-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/core/component/event/emitter.ts b/src/core/component/event/emitter.ts index b27d273d6a..d39a7be133 100644 --- a/src/core/component/event/emitter.ts +++ b/src/core/component/event/emitter.ts @@ -19,8 +19,7 @@ export const globalEmitter = new EventEmitter({ wildcard: true }); -const - originalEmit = globalEmitter.emit.bind(globalEmitter); +const originalEmit = globalEmitter.emit.bind(globalEmitter); globalEmitter.emit = (event: string, ...args) => { const res = originalEmit(event, ...args); diff --git a/src/core/component/field/README.md b/src/core/component/field/README.md index f9f05ed5c7..3c27e16703 100644 --- a/src/core/component/field/README.md +++ b/src/core/component/field/README.md @@ -2,7 +2,7 @@ This module provides an API to initialize component fields to a component instance. -## What differences between fields and system fields? +## What Are the Differences Between Fields and System Fields? The main difference between fields and system fields in V4Fire is that any changes to a regular field can trigger a re-render of the component template. diff --git a/src/core/component/render/daemon/index.ts b/src/core/component/render/daemon/index.ts index 2eeb8e0963..c1301f892f 100644 --- a/src/core/component/render/daemon/index.ts +++ b/src/core/component/render/daemon/index.ts @@ -19,8 +19,7 @@ import type { Task } from 'core/component/render/daemon/interface'; export * from 'core/component/render/daemon/const'; export * from 'core/component/render/daemon/interface'; -const - opts = config.asyncRender; +const opts = config.asyncRender; let inProgress = false, @@ -82,8 +81,7 @@ function run(): void { continue; } - const - canRender = val.task(); + const canRender = val.task(); const exec = (canRender: unknown) => { if (Object.isTruly(canRender)) { diff --git a/src/core/component/traverse/index.ts b/src/core/component/traverse/index.ts index 7a7d75bf91..f51beb5117 100644 --- a/src/core/component/traverse/index.ts +++ b/src/core/component/traverse/index.ts @@ -18,8 +18,7 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function getNormalParent(component: ComponentInterface): ComponentInterface | null { - let - normalParent: Nullable = component.$parent; + let normalParent: Nullable = component.$parent; while ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition From 7b77feba94cc6b341ce608a7825326fe5571ccea Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 16:50:10 +0300 Subject: [PATCH 097/334] chore: stylish fixes --- src/components/traits/README.md | 2 +- src/core/cache/decorators/hydration/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/traits/README.md b/src/components/traits/README.md index fdf6b05b98..f79232b99b 100644 --- a/src/components/traits/README.md +++ b/src/components/traits/README.md @@ -8,7 +8,7 @@ Consequently, in TypeScript, we have to implement every method in our classes, even if the implementation remains unchanged. This is where traits become useful. -## How it works? +## How Does It Work? Let's list the steps to create a trait. diff --git a/src/core/cache/decorators/hydration/README.md b/src/core/cache/decorators/hydration/README.md index afd2f12cac..d5e1c016a6 100644 --- a/src/core/cache/decorators/hydration/README.md +++ b/src/core/cache/decorators/hydration/README.md @@ -2,7 +2,7 @@ This module provides a decorator that enables data from the hydration store to be integrated with a cache structure. -## How it works +## How Does It Work? The decorator creates a caching wrapper that retrieves initial data from the hydration store. After the first retrieval, the data in the hydration store is deleted. From 16105ea2c2469e4080fe3b923263f0f5ef9469ff Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 17:30:35 +0300 Subject: [PATCH 098/334] chore: updated log --- CHANGELOG.md | 21 +++++++++++++++++++++ src/components/friends/field/CHANGELOG.md | 10 ++++++++++ src/components/friends/sync/CHANGELOG.md | 6 ++++++ src/components/super/i-block/CHANGELOG.md | 8 ++++++++ src/core/component/accessor/CHANGELOG.md | 6 ++++++ src/core/component/decorators/CHANGELOG.md | 10 ++++++++++ src/core/component/functional/CHANGELOG.md | 6 ++++++ src/core/component/meta/CHANGELOG.md | 6 ++++++ 8 files changed, 73 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 927af4b8d4..b109be1a05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,27 @@ Changelog _Note: Gaps between patch versions are faulty, broken or test releases._ +## v4.0.0-beta.?? (2024-08-23) + +#### :boom: Breaking Change + +* The `Async` module has been moved to `V4/Core` + +#### :rocket: New Feature + +* Added the `partial` parameter for the declaration of components consisting of multiple classes `core/component/meta` +* Added a new method for efficient access to the field store `getFieldsStore` `components/friends/field` +* Introduced a new type of caching: `'forever'` `core/component/accessors` +* Added the `test` parameter for fine-tuning watchers `core/component/decorators` + +#### :house: Internal + +* `iBlock`: + * Set all friend classes now through getters with `cache: 'forever'` + * Modules for friend classes are now loaded lazily + +* Performance improvements + ## v4.0.0-beta.126 (2024-08-23) #### :rocket: New Feature diff --git a/src/components/friends/field/CHANGELOG.md b/src/components/friends/field/CHANGELOG.md index a2fb7ee837..07d7ffc414 100644 --- a/src/components/friends/field/CHANGELOG.md +++ b/src/components/friends/field/CHANGELOG.md @@ -9,6 +9,16 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-08-23) + +#### :rocket: New Feature + +* Added a new method for efficient access to the field store `getFieldsStore` + +#### :house: Internal + +* Performance improvements + ## v4.0.0-alpha.1 (2022-12-14) #### :boom: Breaking Change diff --git a/src/components/friends/sync/CHANGELOG.md b/src/components/friends/sync/CHANGELOG.md index a87986e741..1bb9f5bbbb 100644 --- a/src/components/friends/sync/CHANGELOG.md +++ b/src/components/friends/sync/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-08-23) + +#### :house: Internal + +* Performance improvements + ## v4.0.0-alpha.1 (2022-12-14) #### :boom: Breaking Change diff --git a/src/components/super/i-block/CHANGELOG.md b/src/components/super/i-block/CHANGELOG.md index ee71aa7525..038b700e4c 100644 --- a/src/components/super/i-block/CHANGELOG.md +++ b/src/components/super/i-block/CHANGELOG.md @@ -9,6 +9,14 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-08-23) + +#### :house: Internal + +* Set all friend classes now through getters with `cache: 'forever'` +* Modules for friend classes are now loaded lazily +* Performance improvements + ## v4.0.0-beta.117 (2024-07-31) #### :house: Internal diff --git a/src/core/component/accessor/CHANGELOG.md b/src/core/component/accessor/CHANGELOG.md index ee7623b330..93dfeebd37 100644 --- a/src/core/component/accessor/CHANGELOG.md +++ b/src/core/component/accessor/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-08-23) + +#### :rocket: New Feature + +* Introduced a new type of caching: `'forever'` + ## v4.0.0-beta.106 (2024-06-25) #### :bug: Bug Fix diff --git a/src/core/component/decorators/CHANGELOG.md b/src/core/component/decorators/CHANGELOG.md index c9f188c7f8..41c633bdd3 100644 --- a/src/core/component/decorators/CHANGELOG.md +++ b/src/core/component/decorators/CHANGELOG.md @@ -9,6 +9,16 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-08-23) + +#### :rocket: New Feature + +* Added the `test` parameter for fine-tuning watchers + +#### :house: Internal + +* Performance improvements + ## v4.0.0-beta.121.the-phantom-menace (2024-08-05) #### :rocket: New Feature diff --git a/src/core/component/functional/CHANGELOG.md b/src/core/component/functional/CHANGELOG.md index 2e9ce8c704..9a6b09df3c 100644 --- a/src/core/component/functional/CHANGELOG.md +++ b/src/core/component/functional/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-08-23) + +#### :house: Internal + +* Performance improvements + ## v4.0.0-beta.121.the-phantom-menace (2024-08-05) #### :bug: Bug Fix diff --git a/src/core/component/meta/CHANGELOG.md b/src/core/component/meta/CHANGELOG.md index 223b577426..0ef2869842 100644 --- a/src/core/component/meta/CHANGELOG.md +++ b/src/core/component/meta/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-08-23) + +#### :rocket: New Feature + +* Added the `partial` parameter for the declaration of components consisting of multiple classes + ## 4.0.0-beta.71 (2024-03-12) #### :rocket: New Feature From 650dfa968587e2e1ccab18abfb45081eca16626e Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 3 Sep 2024 17:36:26 +0300 Subject: [PATCH 099/334] chore: updated log --- CHANGELOG.md | 4 +++- build/CHANGELOG.md | 2 ++ build/webpack/CHANGELOG.md | 6 ++++++ src/components/friends/field/CHANGELOG.md | 2 +- src/components/friends/sync/CHANGELOG.md | 2 +- src/components/super/i-block/CHANGELOG.md | 2 +- src/core/component/accessor/CHANGELOG.md | 2 +- src/core/component/decorators/CHANGELOG.md | 2 +- src/core/component/functional/CHANGELOG.md | 2 +- src/core/component/meta/CHANGELOG.md | 2 +- 10 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b109be1a05..17d9f0c0e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Changelog _Note: Gaps between patch versions are faulty, broken or test releases._ -## v4.0.0-beta.?? (2024-08-23) +## v4.0.0-beta.?? (2024-??-??) #### :boom: Breaking Change @@ -30,6 +30,8 @@ _Note: Gaps between patch versions are faulty, broken or test releases._ * Set all friend classes now through getters with `cache: 'forever'` * Modules for friend classes are now loaded lazily +* Apply the `symbol-generator-loader` consistently to optimize Runtime performance `build/webpack` +* A lot of TypeScript type fixes * Performance improvements ## v4.0.0-beta.126 (2024-08-23) diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md index ced9df0230..a20882eefb 100644 --- a/build/CHANGELOG.md +++ b/build/CHANGELOG.md @@ -9,6 +9,8 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + ## v4.0.0-beta.125 (2024-08-12) #### :bug: Bug Fix diff --git a/build/webpack/CHANGELOG.md b/build/webpack/CHANGELOG.md index a4631cbf3f..c5258ec546 100644 --- a/build/webpack/CHANGELOG.md +++ b/build/webpack/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :house: Internal + +Apply the `symbol-generator-loader` consistently to optimize Runtime performance + ## v4.0.0-beta.26 (2023-09-20) #### :rocket: New Feature diff --git a/src/components/friends/field/CHANGELOG.md b/src/components/friends/field/CHANGELOG.md index 07d7ffc414..174774b3bc 100644 --- a/src/components/friends/field/CHANGELOG.md +++ b/src/components/friends/field/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v4.0.0-beta.?? (2024-08-23) +## v4.0.0-beta.?? (2024-??-??) #### :rocket: New Feature diff --git a/src/components/friends/sync/CHANGELOG.md b/src/components/friends/sync/CHANGELOG.md index 1bb9f5bbbb..723b1fc4ea 100644 --- a/src/components/friends/sync/CHANGELOG.md +++ b/src/components/friends/sync/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v4.0.0-beta.?? (2024-08-23) +## v4.0.0-beta.?? (2024-??-??) #### :house: Internal diff --git a/src/components/super/i-block/CHANGELOG.md b/src/components/super/i-block/CHANGELOG.md index 038b700e4c..36b2e98b9e 100644 --- a/src/components/super/i-block/CHANGELOG.md +++ b/src/components/super/i-block/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v4.0.0-beta.?? (2024-08-23) +## v4.0.0-beta.?? (2024-??-??) #### :house: Internal diff --git a/src/core/component/accessor/CHANGELOG.md b/src/core/component/accessor/CHANGELOG.md index 93dfeebd37..afe5924545 100644 --- a/src/core/component/accessor/CHANGELOG.md +++ b/src/core/component/accessor/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v4.0.0-beta.?? (2024-08-23) +## v4.0.0-beta.?? (2024-??-??) #### :rocket: New Feature diff --git a/src/core/component/decorators/CHANGELOG.md b/src/core/component/decorators/CHANGELOG.md index 41c633bdd3..5bcd1716f6 100644 --- a/src/core/component/decorators/CHANGELOG.md +++ b/src/core/component/decorators/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v4.0.0-beta.?? (2024-08-23) +## v4.0.0-beta.?? (2024-??-??) #### :rocket: New Feature diff --git a/src/core/component/functional/CHANGELOG.md b/src/core/component/functional/CHANGELOG.md index 9a6b09df3c..a6c7159a04 100644 --- a/src/core/component/functional/CHANGELOG.md +++ b/src/core/component/functional/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v4.0.0-beta.?? (2024-08-23) +## v4.0.0-beta.?? (2024-??-??) #### :house: Internal diff --git a/src/core/component/meta/CHANGELOG.md b/src/core/component/meta/CHANGELOG.md index 0ef2869842..2a98998bbf 100644 --- a/src/core/component/meta/CHANGELOG.md +++ b/src/core/component/meta/CHANGELOG.md @@ -9,7 +9,7 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v4.0.0-beta.?? (2024-08-23) +## v4.0.0-beta.?? (2024-??-??) #### :rocket: New Feature From d68da6aa005938842b132541440b867faccedb76 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 4 Sep 2024 16:16:22 +0300 Subject: [PATCH 100/334] fix: fixed some bugs --- src/components/friends/field/get.ts | 27 ++++++++++++++++--- .../component/decorators/component/index.ts | 10 ++++--- src/core/component/decorators/factory.ts | 4 +-- src/core/component/reflect/constructor.ts | 9 ++++--- src/core/component/reflect/property.ts | 2 +- 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index 7360162fae..35f33fddd3 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -126,7 +126,15 @@ export function getField( } if (chunks.length === 1) { - res = getter != null ? getter(chunks[0], res) : (res)[chunks[0]]; + const key = chunks[0]; + + if (getter != null) { + res = getter(key, res); + + } else { + const obj = res; + res = key in obj ? obj[key] : undefined; + } } else { const hasNoProperty = chunks.some((key) => { @@ -135,10 +143,23 @@ export function getField( } if (Object.isPromiseLike(res) && !(key in res)) { - res = res.then((res) => getter != null ? getter(key, res) : (res)[key]); + res = res.then((res) => { + if (getter != null) { + return getter(key, res); + } + + const obj = res; + return key in obj ? obj[key] : undefined; + }); } else { - res = getter != null ? getter(key, res) : (res)[key]; + if (getter != null) { + res = getter(key, res); + + } else { + const obj = res; + res = key in obj ? obj[key] : undefined; + } } return false; diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 000ffbd35a..c15006bae2 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -88,7 +88,9 @@ export function component(opts?: ComponentOptions): Function { Object.defineProperty(componentInfo.parent, OVERRIDDEN, {value: true}); } - initEmitter.emit('bindConstructor', componentOriginName); + const regEvent = `constructor.${componentOriginName}.componentInfo.layer`; + + initEmitter.emit('bindConstructor', componentOriginName, regEvent); if (isPartial) { pushToInitList(() => { @@ -99,7 +101,7 @@ export function component(opts?: ComponentOptions): Function { components.set(componentOriginName, meta); } - initEmitter.once(`constructor.${componentOriginName}`, () => { + initEmitter.once(regEvent, () => { addMethodsToMeta(components.get(componentOriginName)!, target); }); }); @@ -134,7 +136,7 @@ export function component(opts?: ComponentOptions): Function { function regComponent() { registerParentComponents(componentInfo); - let rawMeta = components.get(componentNormalizedName); + let rawMeta = !hasSameOrigin ? components.get(componentNormalizedName) : null; if (rawMeta == null) { rawMeta = createMeta(componentInfo); @@ -182,7 +184,7 @@ export function component(opts?: ComponentOptions): Function { components.set(target, meta); } - initEmitter.emit(`constructor.${componentNormalizedName}`, { + initEmitter.emit(regEvent, { meta, parentMeta: componentInfo.parentMeta }); diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index ef889711cc..9aa372ad70 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -35,7 +35,7 @@ export function paramsFactory( transformer?: ParamsFactoryTransformer ): FactoryTransformer { return (params: Dictionary = {}) => (_: object, key: string, desc?: PropertyDescriptor) => { - initEmitter.once('bindConstructor', (componentName) => { + initEmitter.once('bindConstructor', (componentName, regEvent) => { metaPointers[componentName] = metaPointers[componentName] ?? Object.createDict(); const link = metaPointers[componentName]; @@ -45,7 +45,7 @@ export function paramsFactory( } link[key] = true; - initEmitter.once(`constructor.${componentName}`, decorate); + initEmitter.once(regEvent, decorate); }); function decorate({meta}: {meta: ComponentMeta}): void { diff --git a/src/core/component/reflect/constructor.ts b/src/core/component/reflect/constructor.ts index 0e71dd611e..984138c926 100644 --- a/src/core/component/reflect/constructor.ts +++ b/src/core/component/reflect/constructor.ts @@ -63,7 +63,9 @@ export function getInfoFromConstructor( constructor: ComponentConstructor, declParams?: ComponentOptions ): ComponentConstructorInfo { - const partial = declParams?.partial?.dasherize(); + const + partial = declParams?.partial?.dasherize(), + layer = declParams?.layer; if (partial != null) { let info = partialInfo.get(partial); @@ -74,6 +76,7 @@ export function getInfoFromConstructor( info = { name: partial, componentName: partial, + layer, constructor, params: {...declParams, partial}, @@ -100,9 +103,7 @@ export function getInfoFromConstructor( return info; } - const - name = declParams?.name ?? getComponentName(constructor), - layer = declParams?.layer; + const name = declParams?.name ?? getComponentName(constructor); let {parent, parentParams} = getParent(); diff --git a/src/core/component/reflect/property.ts b/src/core/component/reflect/property.ts index 0ea4841092..c9d8b09296 100644 --- a/src/core/component/reflect/property.ts +++ b/src/core/component/reflect/property.ts @@ -69,7 +69,7 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr return true; } - obj = obj[chunk]; + obj = chunk in obj ? obj[chunk] : undefined; if (obj != null && typeof obj === 'object' && 'componentName' in obj) { component = obj; From c09bee96fc2b3a1097ddeec7d40a52b0188dfd36 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 4 Sep 2024 16:19:53 +0300 Subject: [PATCH 101/334] fix: fixed a typo --- src/core/component/decorators/component/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index c15006bae2..d22cb4aa86 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -88,7 +88,7 @@ export function component(opts?: ComponentOptions): Function { Object.defineProperty(componentInfo.parent, OVERRIDDEN, {value: true}); } - const regEvent = `constructor.${componentOriginName}.componentInfo.layer`; + const regEvent = `constructor.${componentOriginName}.${componentInfo.layer}`; initEmitter.emit('bindConstructor', componentOriginName, regEvent); From d3acd3265e79c263e414d7a20464a5b7bd4ba96b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 4 Sep 2024 18:48:16 +0300 Subject: [PATCH 102/334] fix: fixed prop default values & refactoring --- src/core/component/const/cache.ts | 6 +++-- .../component/decorators/component/index.ts | 4 ++-- src/core/component/decorators/factory.ts | 13 ++++------- src/core/component/meta/inherit.ts | 10 ++++---- src/core/component/prop/init.ts | 23 +++++++++---------- 5 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/core/component/const/cache.ts b/src/core/component/const/cache.ts index a86fb2e809..e85c64e17e 100644 --- a/src/core/component/const/cache.ts +++ b/src/core/component/const/cache.ts @@ -61,9 +61,11 @@ export const componentRegInitializers = Object.createDict(); export const componentRenderFactories = Object.createDict(); /** - * A dictionary containing component pointers for metatables + * A map representing a dictionary where the key is the component name, + * and the value is a Set of all keys that have a decorator specified + * within the component declaration */ -export const metaPointers = Object.createDict>(); +export const componentDecoratedKeys = Object.createDict>(); /** * Globally initialized application (not supported in SSR) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index d22cb4aa86..ab63a35248 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -88,9 +88,9 @@ export function component(opts?: ComponentOptions): Function { Object.defineProperty(componentInfo.parent, OVERRIDDEN, {value: true}); } - const regEvent = `constructor.${componentOriginName}.${componentInfo.layer}`; + const regEvent = `constructor.${componentNormalizedName}.${componentInfo.layer}`; - initEmitter.emit('bindConstructor', componentOriginName, regEvent); + initEmitter.emit('bindConstructor', componentNormalizedName, regEvent); if (isPartial) { pushToInitList(() => { diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index 9aa372ad70..affa20380e 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -11,7 +11,7 @@ import { defProp } from 'core/const/props'; import { isStore } from 'core/component/reflect'; import { initEmitter } from 'core/component/event'; -import { metaPointers } from 'core/component/const'; +import { componentDecoratedKeys } from 'core/component/const'; import { invertedFieldMap, tiedFieldMap } from 'core/component/decorators/const'; import type { ComponentMeta, ComponentProp, ComponentField } from 'core/component/interface'; @@ -36,15 +36,10 @@ export function paramsFactory( ): FactoryTransformer { return (params: Dictionary = {}) => (_: object, key: string, desc?: PropertyDescriptor) => { initEmitter.once('bindConstructor', (componentName, regEvent) => { - metaPointers[componentName] = metaPointers[componentName] ?? Object.createDict(); + const decoratedKeys = componentDecoratedKeys[componentName] ?? new Set(); + componentDecoratedKeys[componentName] = decoratedKeys; - const link = metaPointers[componentName]; - - if (link == null) { - return; - } - - link[key] = true; + decoratedKeys.add(key); initEmitter.once(regEvent, decorate); }); diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index c78e7ee0ad..823db3ac46 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { metaPointers, PARENT } from 'core/component/const'; +import { componentDecoratedKeys, PARENT } from 'core/component/const'; import type { ModDeclVal, FieldWatcher } from 'core/component/interface'; import type { ComponentMeta } from 'core/component/meta/interface'; @@ -19,7 +19,7 @@ import type { ComponentMeta } from 'core/component/meta/interface'; * @param parentMeta */ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): ComponentMeta { - const metaPointer = metaPointers[meta.componentName]; + const decoratedKeys = componentDecoratedKeys[meta.componentName]; Object.assign(meta.tiedFields, parentMeta.tiedFields); @@ -50,7 +50,7 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com return; } - if (metaPointer == null || !metaPointer[propName]) { + if (decoratedKeys == null || !decoratedKeys.has(propName)) { current[propName] = parent; return; } @@ -72,7 +72,7 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com return; } - if (metaPointer == null || !metaPointer[fieldName]) { + if (decoratedKeys == null || !decoratedKeys.has(fieldName)) { current[fieldName] = parent; return; } @@ -107,7 +107,7 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com return; } - if (metaPointer == null || !metaPointer[methodName]) { + if (decoratedKeys == null || !decoratedKeys.has(methodName)) { current[methodName] = {...parent}; return; } diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index c12932392a..27043c99a2 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -7,9 +7,8 @@ */ import { DEFAULT_WRAPPER } from 'core/component/const'; -import type { ComponentInterface } from 'core/component/interface'; -import { isTypeCanBeFunc } from 'core/component/prop/helpers'; +import type { ComponentInterface } from 'core/component/interface'; import type { InitPropsObjectOptions } from 'core/component/prop/interface'; /** @@ -48,7 +47,7 @@ export function initProps( isFunctional = meta.params.functional === true, source: typeof props = p.forceUpdate ? props : attrs; - Object.entries(source).forEach(([name, prop]) => { + Object.entries(source).forEach(([propName, prop]) => { const canSkip = prop == null || !SSR && isFunctional && prop.functional === false; @@ -57,11 +56,11 @@ export function initProps( return; } - unsafe.$activeField = name; + unsafe.$activeField = propName; - let propValue = (from ?? component)[name]; + let propValue = (from ?? component)[propName]; - const getAccessors = unsafe.$attrs[`on:${name}`]; + const getAccessors = unsafe.$attrs[`on:${propName}`]; if (propValue === undefined && Object.isFunction(getAccessors)) { propValue = getAccessors()[0]; @@ -72,7 +71,7 @@ export function initProps( propValue = prop.default; } else { - propValue = meta.instance[name]; + propValue = meta.instance[propName]; if (!Object.isPrimitive(propValue)) { propValue = Object.fastClone(propValue); @@ -84,7 +83,7 @@ export function initProps( if (Object.isFunction(propValue)) { if (opts.saveToStore === true || propValue[DEFAULT_WRAPPER] !== true) { - propValue = isTypeCanBeFunc(prop.type) ? propValue.bind(component) : propValue.call(component); + propValue = prop.type === Function ? propValue.bind(null, component) : propValue(component); needSaveToStore = true; } } @@ -93,15 +92,15 @@ export function initProps( if (propValue === undefined) { if (prop.required) { - throw new TypeError(`Missing required prop: "${name}" at ${componentName}`); + throw new TypeError(`Missing required prop: "${propName}" at ${componentName}`); } } else if (prop.validator != null && !prop.validator(propValue)) { - throw new TypeError(`Invalid prop: custom validator check failed for prop "${name}" at ${componentName}`); + throw new TypeError(`Invalid prop: custom validator check failed for prop "${propName}" at ${componentName}`); } if (needSaveToStore) { - const privateField = `[[${name}]]`; + const privateField = `[[${propName}]]`; if (!opts.forceUpdate) { // Set the property as enumerable so that it can be deleted in the destructor later @@ -113,7 +112,7 @@ export function initProps( }); } - Object.defineProperty(store, name, { + Object.defineProperty(store, propName, { configurable: true, enumerable: true, get: () => opts.forceUpdate ? propValue : store[privateField] From a5d5775cb09cb2f652da086fdda61caf6d6a149a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 5 Sep 2024 13:58:02 +0300 Subject: [PATCH 103/334] fix: fixed a bug with prop default values --- src/core/component/prop/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index 27043c99a2..1f767555c5 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -83,7 +83,7 @@ export function initProps( if (Object.isFunction(propValue)) { if (opts.saveToStore === true || propValue[DEFAULT_WRAPPER] !== true) { - propValue = prop.type === Function ? propValue.bind(null, component) : propValue(component); + propValue = prop.type === Function ? propValue.bind(component) : propValue(component); needSaveToStore = true; } } From 4059f2f065e04fa80ddae42f6883b7a0f07e1795 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 5 Sep 2024 13:59:25 +0300 Subject: [PATCH 104/334] fix: fixed a bug with prop default values --- src/core/component/prop/init.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index 1f767555c5..fc04aec35c 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -83,7 +83,12 @@ export function initProps( if (Object.isFunction(propValue)) { if (opts.saveToStore === true || propValue[DEFAULT_WRAPPER] !== true) { - propValue = prop.type === Function ? propValue.bind(component) : propValue(component); + propValue = prop.type === Function ? propValue : propValue(component); + + if (Object.isFunction(propValue)) { + propValue = propValue.bind(component); + } + needSaveToStore = true; } } From 2c3ccdfc0e73f6668410ccc385301244b7ed5707 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 5 Sep 2024 18:33:04 +0300 Subject: [PATCH 105/334] fix: fixed an issue with getters and their effect --- src/components/friends/field/class.ts | 4 +- src/core/component/accessor/index.ts | 48 ++++++++++++++++++++-- src/core/component/field/index.ts | 1 + src/core/component/field/store.ts | 22 ++++++++++ src/core/component/index.ts | 2 + src/core/component/meta/interface/index.ts | 6 ++- 6 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 src/core/component/field/store.ts diff --git a/src/components/friends/field/class.ts b/src/components/friends/field/class.ts index d2bb0fdf33..cacf548bf7 100644 --- a/src/components/friends/field/class.ts +++ b/src/components/friends/field/class.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type { ComponentInterface } from 'core/component'; +import { getFieldsStore, ComponentInterface } from 'core/component'; import Friend, { fakeMethods } from 'components/friends/friend'; import { getField } from 'components/friends/field/get'; @@ -40,7 +40,7 @@ class Field extends Friend { getFieldsStore(component?: T): T; getFieldsStore(component?: T): T; getFieldsStore(component: typeof this.ctx = this.ctx): object { - return Object.cast(component.isFunctionalWatchers || this.lfc.isBeforeCreate() ? component.$fields : component); + return getFieldsStore(component); } } diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index 7816c697db..8920cf4634 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -16,6 +16,7 @@ import * as gc from 'core/component/gc'; import { deprecate } from 'core/functools/deprecation'; import { beforeHooks } from 'core/component/const'; +import { getFieldsStore } from 'core/component/field'; import { cacheStatus } from 'core/component/watch'; import type { ComponentInterface } from 'core/component/interface'; @@ -68,7 +69,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { meta, // eslint-disable-next-line deprecation/deprecation - meta: {params: {deprecatedProps}, tiedFields}, + meta: {params: {deprecatedProps}, fields, tiedFields}, $destructors } = component.unsafe; @@ -114,6 +115,9 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { if (canSkip) { const tiedWith = tiedFields[name]; + // If the getter is not initialized, + // then the related fields should also be removed to avoid registering a watcher for cache invalidation, + // as it will not be used if (tiedWith != null) { delete tiedFields[tiedWith]; delete tiedFields[name]; @@ -122,11 +126,16 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { return; } + // In the `tiedFields` dictionary, + // the names of the getters themselves are also stored as keys with their related fields as values. + // This is done for convenience. + // However, watchers for cache invalidation of the getter will be created for all keys in `tiedFields`. + // Since it's not possible to watch the getter itself, we need to remove the key with its name. delete tiedFields[name]; // eslint-disable-next-line func-style const get = function get(this: typeof component): unknown { - const {hook} = this; + const {unsafe, hook} = this; const canUseForeverCache = computed.cache === 'forever'; @@ -147,8 +156,39 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { if (needEffect) { meta.watchDependencies.get(name)?.forEach((path) => { - // @ts-ignore (effect) - void this[path]; + let firstChunk: string; + + if (Object.isString(path)) { + if (path.includes('.')) { + const chunks = path.split('.'); + + firstChunk = path[0]; + + if (chunks.length === 1) { + path = firstChunk; + } + + } else { + firstChunk = path; + } + + } else { + firstChunk = path[0]; + + if (path.length === 1) { + path = firstChunk; + } + } + + const store = fields[firstChunk] != null ? getFieldsStore(unsafe) : unsafe; + + if (Object.isArray(path)) { + void Object.get(store, path); + + } else if (path in store) { + // @ts-ignore (effect) + void store[path]; + } }); ['Store', 'Prop'].forEach((postfix) => { diff --git a/src/core/component/field/index.ts b/src/core/component/field/index.ts index 1499b289f5..2bbbec88f6 100644 --- a/src/core/component/field/index.ts +++ b/src/core/component/field/index.ts @@ -12,4 +12,5 @@ */ export * from 'core/component/field/init'; +export * from 'core/component/field/store'; export * from 'core/component/field/interface'; diff --git a/src/core/component/field/store.ts b/src/core/component/field/store.ts new file mode 100644 index 0000000000..168336967b --- /dev/null +++ b/src/core/component/field/store.ts @@ -0,0 +1,22 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { beforeHooks } from 'core/component/const'; +import type { ComponentInterface } from 'core/component/interface'; + +/** + * Returns a reference to the storage object for the fields of the passed component + * @param [component] + */ +export function getFieldsStore(component: ComponentInterface['unsafe']): object { + if (SSR || component.meta.params.functional === true || beforeHooks[component.hook] != null) { + return component.$fields; + } + + return component; +} diff --git a/src/core/component/index.ts b/src/core/component/index.ts index c80f865b52..701a51200d 100644 --- a/src/core/component/index.ts +++ b/src/core/component/index.ts @@ -49,6 +49,8 @@ export { } from 'core/component/event'; +export { getFieldsStore } from 'core/component/field'; + export * from 'core/component/reflect'; export * from 'core/component/decorators'; export * from 'core/component/interface'; diff --git a/src/core/component/meta/interface/index.ts b/src/core/component/meta/interface/index.ts index 43d572f181..ad5459d07a 100644 --- a/src/core/component/meta/interface/index.ts +++ b/src/core/component/meta/interface/index.ts @@ -100,7 +100,11 @@ export interface ComponentMeta { systemFields: Dictionary; /** - * A dictionary containing the component fields that have a "Store" postfix in their name + * A dictionary containing the component properties as well as properties that are related to them. + * For example: + * + * `foo → fooStore` + * `fooStore → foo` */ tiedFields: Dictionary; From e77db4dfb798c13f300cec17be829ba515e6ce57 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 5 Sep 2024 18:34:13 +0300 Subject: [PATCH 106/334] chore: improved doc --- .../component/decorators/component/README.md | 16 +++++++++++++--- .../component/decorators/component/index.ts | 19 +++++++++++++------ src/core/component/meta/interface/options.ts | 16 +++++++++++++--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/core/component/decorators/component/README.md b/src/core/component/decorators/component/README.md index d9138bd812..07d60aa106 100644 --- a/src/core/component/decorators/component/README.md +++ b/src/core/component/decorators/component/README.md @@ -87,7 +87,7 @@ The name of the component to which this one belongs. This option is used when we want to split the component into multiple classes. Please note that in partial classes, -there should be no conflicts in methods or properties with other partial classes of this component. +there should be no overrides in methods or properties with other partial classes of this component. ```typescript import iBlock, { component, prop } from 'components/super/i-block/i-block'; @@ -98,9 +98,19 @@ class bExampleProps extends iBlock { value: number = 0; } -@component() -class bExample extends bExampleProps { +@component({partial: 'bExample'}) +class bExampleAPI extends bExampleProps { + getName(): string { + return this.meta.componentName; + } +} +@component() +class bExample extends bExampleAPI { + // This override will not work correctly, as it overrides what was added within the partial class + override getName(): string { + return this.meta.componentName; + } } ``` diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index ab63a35248..71c4a77923 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -82,18 +82,21 @@ export function component(opts?: ComponentOptions): Function { const componentOriginName = componentInfo.name, componentNormalizedName = componentInfo.componentName, - hasSameOrigin = !isPartial && componentOriginName === componentInfo.parentParams?.name; + isParentLayerOverride = !isPartial && componentOriginName === componentInfo.parentParams?.name; - if (hasSameOrigin) { + if (isParentLayerOverride) { Object.defineProperty(componentInfo.parent, OVERRIDDEN, {value: true}); } + // Add information about the layer in which the component is described + // to correctly handle situations where the component is overridden in child layers of the application const regEvent = `constructor.${componentNormalizedName}.${componentInfo.layer}`; initEmitter.emit('bindConstructor', componentNormalizedName, regEvent); if (isPartial) { pushToInitList(() => { + // Partial classes reuse the same metaobject let meta = components.get(componentOriginName); if (meta == null) { @@ -136,14 +139,18 @@ export function component(opts?: ComponentOptions): Function { function regComponent() { registerParentComponents(componentInfo); - let rawMeta = !hasSameOrigin ? components.get(componentNormalizedName) : null; + // The metaobject might have already been created by partial classes or in the case of a smart component + let rawMeta = !isParentLayerOverride ? components.get(componentNormalizedName) : null; + // If the metaobject has not been created, it should be created now if (rawMeta == null) { rawMeta = createMeta(componentInfo); components.set(componentNormalizedName, rawMeta); + // If the metaobject has already been created, we create its shallow copy with some fields overridden. + // This is necessary because smart components use the same metaobject. } else { - const newTarget = target !== rawMeta.constructor; + const hasNewTarget = target !== rawMeta.constructor; rawMeta = Object.create(rawMeta, { constructor: { @@ -157,7 +164,7 @@ export function component(opts?: ComponentOptions): Function { configurable: true, enumerable: true, writable: true, - value: newTarget ? getComponentMods(componentInfo) : rawMeta.mods + value: hasNewTarget ? getComponentMods(componentInfo) : rawMeta.mods }, params: { @@ -171,7 +178,7 @@ export function component(opts?: ComponentOptions): Function { if (rawMeta != null && componentInfo.parentMeta != null) { inheritParams(rawMeta, componentInfo.parentMeta); - if (newTarget) { + if (hasNewTarget) { inheritMods(rawMeta, componentInfo.parentMeta); } } diff --git a/src/core/component/meta/interface/options.ts b/src/core/component/meta/interface/options.ts index 45768e3000..8d5e726f87 100644 --- a/src/core/component/meta/interface/options.ts +++ b/src/core/component/meta/interface/options.ts @@ -37,7 +37,7 @@ export interface ComponentOptions { * This option is used when we want to split the component into multiple classes. * * Please note that in partial classes, - * there should be no conflicts in methods or properties with other partial classes of this component. + * there should be no overrides in methods or properties with other partial classes of this component. * * @example * ```typescript @@ -47,9 +47,19 @@ export interface ComponentOptions { * value: number = 0; * } * - * @component() - * class bExample extends bExampleProps { + * @component({partial: 'bExample'}) + * class bExampleAPI extends bExampleProps { + * getName(): string { + * return this.meta.componentName; + * } + * } * + * @component() + * class bExample extends bExampleAPI { + * // This override will not work correctly, as it overrides what was added within the partial class + * override getName(): string { + * return this.meta.componentName; + * } * } * ``` */ From e321f82b556b1b3c0fe307acaceea790835182cc Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 5 Sep 2024 18:35:08 +0300 Subject: [PATCH 107/334] chore: updated log --- build/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md index a20882eefb..bd14fedeaf 100644 --- a/build/CHANGELOG.md +++ b/build/CHANGELOG.md @@ -11,6 +11,10 @@ Changelog ## v4.0.0-beta.?? (2024-??-??) +#### :house: Internal + +* Apply the `symbol-generator-loader` consistently to optimize Runtime performance + ## v4.0.0-beta.125 (2024-08-12) #### :bug: Bug Fix From 14fcdacf1aac808722d174976d3daf953c66799b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 5 Sep 2024 18:42:33 +0300 Subject: [PATCH 108/334] chore: improved doc --- src/core/component/meta/fill.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 65651c47fa..5d889a4bf0 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -17,9 +17,9 @@ import { addMethodsToMeta } from 'core/component/meta/method'; import type { ComponentConstructor, ComponentMeta, ModVal } from 'core/component/interface'; const - BLUEPRINT = Symbol('The meta blueprint'), INSTANCE = Symbol('The component instance'), - ALREADY_PASSED = Symbol('This constructor is already passed'); + BLUEPRINT = Symbol('The metaobject blueprint'), + ALREADY_FILLED = Symbol('This constructor has already been used to populate the metaobject'); /** * Populates the passed metaobject with methods and properties from the specified component class constructor @@ -35,7 +35,7 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor } // For smart components, this method can be called more than once - const isFirstFill = !constructor.hasOwnProperty(ALREADY_PASSED); + const isFirstFill = !constructor.hasOwnProperty(ALREADY_FILLED); if (Object.isDictionary(meta.params.functional) && meta[BLUEPRINT] == null) { Object.defineProperty(meta, BLUEPRINT, { @@ -89,6 +89,8 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor } }); + // Creating an instance of a component is not a free operation. + // If it is not immediately necessary, we execute it in the background during idle time. requestIdleCallback(() => { void meta.instance; }); @@ -305,7 +307,7 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor }); } - Object.defineProperty(constructor, ALREADY_PASSED, {value: true}); + Object.defineProperty(constructor, ALREADY_FILLED, {value: true}); return meta; } From 2a4c38afa60d9e00c135d0e0441012e51808041c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 5 Sep 2024 18:45:02 +0300 Subject: [PATCH 109/334] chore: improved doc --- src/core/component/init/states/before-create.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index a43d5afb2f..60bc06e7c1 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -67,6 +67,8 @@ export function beforeCreateState( unsafe.$systemFields = {}; unsafe.$modifiedFields = {}; unsafe.$renderCounter = 0; + + // A stub for the correct functioning of $parent unsafe.$restArgs = undefined; unsafe.async = new Async(component); From d17a546f38c4da6a7ddcd0084b94b6ad0dcc36e9 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 6 Sep 2024 15:06:11 +0300 Subject: [PATCH 110/334] fix: fixed an issue with @fields --- src/components/friends/sync/link.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index c5ad9f1e2a..7caae3c9b1 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -388,7 +388,12 @@ export function link( this.field.set(destPath, resolveVal); } else if (info.type === 'field') { - this.field.getFieldsStore(info.ctx)[destPath] = resolveVal; + const store = this.field.getFieldsStore(info.ctx); + store[destPath] = resolveVal; + + if (store !== info.ctx) { + info.ctx[destPath] = resolveVal; + } } else { info.ctx[destPath] = resolveVal; From 5286a653ed2fe94411854e0f3dfb0e836acc39b2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 9 Sep 2024 15:44:37 +0300 Subject: [PATCH 111/334] chore: use V4_COMPONENT to check whether an object is a component --- src/components/friends/field/delete.ts | 4 ++-- src/components/friends/field/get.ts | 4 ++-- src/components/friends/field/set.ts | 4 ++-- src/components/friends/vdom/vnode.ts | 4 ++-- src/core/component/const/symbols.ts | 11 ++++++++--- src/core/component/index.ts | 1 + src/core/component/init/states/before-create.ts | 2 ++ src/core/component/interface/component/component.ts | 6 ++++++ src/core/component/reflect/property.ts | 6 ++++-- 9 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index 2964d2cc09..aa48a721e1 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -9,7 +9,7 @@ import { unwrap } from 'core/object/watch'; import type Friend from 'components/friends/friend'; -import iBlock, { getPropertyInfo } from 'components/super/i-block/i-block'; +import iBlock, { getPropertyInfo, V4_COMPONENT } from 'components/super/i-block/i-block'; import type { KeyGetter } from 'components/friends/field/interface'; @@ -90,7 +90,7 @@ export function deleteField( let isComponent = false; - if ('componentName' in obj && 'unsafe' in obj) { + if (V4_COMPONENT in obj) { ctx = (obj).unsafe; isComponent = true; } diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index 35f33fddd3..ce1f692beb 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -7,7 +7,7 @@ */ import type Field from 'components/friends/field'; -import iBlock, { getPropertyInfo } from 'components/super/i-block/i-block'; +import iBlock, { getPropertyInfo, V4_COMPONENT } from 'components/super/i-block/i-block'; import type { ValueGetter } from 'components/friends/field/interface'; @@ -83,7 +83,7 @@ export function getField( let isComponent = false; - if ('componentName' in obj && 'unsafe' in obj) { + if (V4_COMPONENT in obj) { ctx = (obj).unsafe; isComponent = true; } diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index 261950f429..d75b3c0c1b 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -9,7 +9,7 @@ import { unwrap } from 'core/object/watch'; import type Friend from 'components/friends/friend'; -import iBlock, { getPropertyInfo } from 'components/super/i-block/i-block'; +import iBlock, { getPropertyInfo, V4_COMPONENT } from 'components/super/i-block/i-block'; import type { KeyGetter } from 'components/friends/field/interface'; @@ -88,7 +88,7 @@ export function setField( let isComponent = false; - if ('componentName' in obj && 'unsafe' in obj) { + if (V4_COMPONENT in obj) { ctx = (obj).unsafe; isComponent = true; } diff --git a/src/components/friends/vdom/vnode.ts b/src/components/friends/vdom/vnode.ts index 33a108710c..9ecc47c6c6 100644 --- a/src/components/friends/vdom/vnode.ts +++ b/src/components/friends/vdom/vnode.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { isComponent } from 'core/component'; +import { isComponent, V4_COMPONENT } from 'core/component'; import { getDirectiveComponent } from 'core/component/directives'; import type { VNode, VNodeVirtualParent } from 'core/component/engines'; @@ -205,7 +205,7 @@ function createVNode( let ctxFromVNode = getDirectiveComponent(vnode); - if (ctxFromVNode?.$parent != null && !('componentName' in ctxFromVNode.$parent)) { + if (ctxFromVNode?.$parent != null && !(V4_COMPONENT in ctxFromVNode.$parent)) { ctxFromVNode = Object.create(ctxFromVNode, { $parent: { enumerable: true, diff --git a/src/core/component/const/symbols.ts b/src/core/component/const/symbols.ts index 3433595158..8cc5c0efb5 100644 --- a/src/core/component/const/symbols.ts +++ b/src/core/component/const/symbols.ts @@ -7,16 +7,21 @@ */ /** - * A flag to mark some function that it’s a generated default wrapper + * A unique symbol used to identify a V4Fire component + */ +export const V4_COMPONENT = Symbol('This is a V4Fire component'); + +/** + * A symbol used as a flag to mark a function as a generated default wrapper */ export const DEFAULT_WRAPPER = Symbol('This function is the generated default wrapper'); /** - * A value to refer the parent instance + * A placeholder object used to refer to the parent instance in a specific context */ export const PARENT = {}; /** - * A symbol for extracting the unique identifier of the async render task + * A symbol used for extracting the unique identifier of the asynchronous render task. */ export const ASYNC_RENDER_ID = Symbol('Async render task identifier'); diff --git a/src/core/component/index.ts b/src/core/component/index.ts index 701a51200d..3a1ec2b5ef 100644 --- a/src/core/component/index.ts +++ b/src/core/component/index.ts @@ -32,6 +32,7 @@ export { isComponent, rootComponents, + V4_COMPONENT, ASYNC_RENDER_ID, PARENT diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 60bc06e7c1..56f7176ea6 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -11,6 +11,7 @@ import Async from 'core/async'; import * as gc from 'core/component/gc'; import watch from 'core/object/watch'; +import { V4_COMPONENT } from 'core/component/const'; import { getComponentContext } from 'core/component/context'; import { forkMeta } from 'core/component/meta'; @@ -51,6 +52,7 @@ export function beforeCreateState( // To avoid TS errors marks all properties as editable const unsafe = Object.cast>(component); + unsafe[V4_COMPONENT] = true; // @ts-ignore (unsafe) unsafe.unsafe = unsafe; diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index 7403689297..ad5d8e7114 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -15,6 +15,7 @@ import type Async from 'core/async'; import type { BoundFn, ProxyCb, EventId } from 'core/async'; import type { AbstractCache } from 'core/cache'; +import { V4_COMPONENT } from 'core/component/const'; import type { ComponentMeta } from 'core/component/meta'; import type { VNode, Slots, ComponentOptions, SetupContext } from 'core/component/engines'; @@ -41,6 +42,11 @@ export abstract class ComponentInterface { */ readonly Component!: ComponentInterface; + /** + * A unique symbol used to identify a V4Fire component + */ + readonly [V4_COMPONENT]: true; + /** * References to the instance of the entire application and its state */ diff --git a/src/core/component/reflect/property.ts b/src/core/component/reflect/property.ts index c9d8b09296..65e44369e4 100644 --- a/src/core/component/reflect/property.ts +++ b/src/core/component/reflect/property.ts @@ -7,10 +7,12 @@ */ import { deprecate } from 'core/functools/deprecation'; -import type { ComponentInterface } from 'core/component/interface'; +import { V4_COMPONENT } from 'core/component/const'; import { isStore, isPrivateField } from 'core/component/reflect/const'; + import type { PropertyInfo, AccessorType } from 'core/component/reflect/interface'; +import type { ComponentInterface } from 'core/component/interface'; /** * Returns an object containing information of the component property by the specified path @@ -71,7 +73,7 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr obj = chunk in obj ? obj[chunk] : undefined; - if (obj != null && typeof obj === 'object' && 'componentName' in obj) { + if (obj != null && typeof obj === 'object' && V4_COMPONENT in obj) { component = obj; rootI = i === chunks.length - 1 ? i : i + 1; } From 676ad224ff43f7172d41d973eaded910f7b09b98 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 9 Sep 2024 15:45:02 +0300 Subject: [PATCH 112/334] chore: added missing API --- src/components/super/i-block/interface.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/components/super/i-block/interface.ts b/src/components/super/i-block/interface.ts index 3b2bdd717b..ac231e14dd 100644 --- a/src/components/super/i-block/interface.ts +++ b/src/components/super/i-block/interface.ts @@ -76,6 +76,15 @@ export interface UnsafeIBlock extends UnsafeCompone // @ts-ignore (access) block: CTX['block']; + // @ts-ignore (access) + get browser(): CTX['browser']; + + // @ts-ignore (access) + get presets(): CTX['presets']; + + // @ts-ignore (access) + get h(): CTX['h']; + // @ts-ignore (access) get moduleLoader(): CTX['moduleLoader']; @@ -126,4 +135,10 @@ export interface UnsafeIBlock extends UnsafeCompone // @ts-ignore (access) waitRef: CTX['waitRef']; + + // @ts-ignore (access) + initInfoRender: CTX['initInfoRender']; + + // @ts-ignore (access) + initDaemons: CTX['initDaemons']; } From 751e9feb4048a5be3df842927a86df7d4224649d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 9 Sep 2024 15:48:12 +0300 Subject: [PATCH 113/334] chore: removed redundant ts-ignore --- src/components/base/b-cache-ssr/b-cache-ssr.ts | 2 -- src/components/base/b-virtual-scroll/b-virtual-scroll.ts | 1 - src/components/form/b-checkbox/b-checkbox.ts | 3 +-- src/components/form/b-input/b-input.ts | 4 ---- src/components/form/b-select-date/b-select-date.ts | 2 -- src/components/form/b-textarea/b-textarea.ts | 4 ---- .../b-friends-module-loader-dummy.ts | 1 - .../pages/p-v4-components-demo/p-v4-components-demo.ts | 1 - 8 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/components/base/b-cache-ssr/b-cache-ssr.ts b/src/components/base/b-cache-ssr/b-cache-ssr.ts index ae79ee2413..c5944d5a4c 100644 --- a/src/components/base/b-cache-ssr/b-cache-ssr.ts +++ b/src/components/base/b-cache-ssr/b-cache-ssr.ts @@ -23,7 +23,6 @@ export * from 'components/base/b-cache-ssr/const'; @component({functional: true}) export default class bCacheSSR extends iBlock { @prop({required: true}) - // @ts-ignore (override) override readonly globalName!: string; override readonly rootTag: string = 'div'; @@ -36,7 +35,6 @@ export default class bCacheSSR extends iBlock { } @system(() => ssrCache) - // @ts-ignore (override) protected override readonly $ssrCache!: AbstractCache; protected override get state(): SuperState { diff --git a/src/components/base/b-virtual-scroll/b-virtual-scroll.ts b/src/components/base/b-virtual-scroll/b-virtual-scroll.ts index 6aa85c46dc..cc0e469de5 100644 --- a/src/components/base/b-virtual-scroll/b-virtual-scroll.ts +++ b/src/components/base/b-virtual-scroll/b-virtual-scroll.ts @@ -155,7 +155,6 @@ export default class bVirtualScroll extends iData implements iItems { readonly requestQuery?: RequestQueryFn; @prop({type: [Object, Array], required: false}) - // @ts-ignore (override) override readonly request?: RequestParams; /** diff --git a/src/components/form/b-checkbox/b-checkbox.ts b/src/components/form/b-checkbox/b-checkbox.ts index 95f3f300c0..3759106403 100644 --- a/src/components/form/b-checkbox/b-checkbox.ts +++ b/src/components/form/b-checkbox/b-checkbox.ts @@ -184,8 +184,7 @@ export default class bCheckbox extends iInput implements iSize { }; @system() - // @ts-ignore (override) - protected override valueStore: this['Value']; + protected override valueStore!: this['Value']; /** @inheritDoc */ declare protected readonly $refs: iInput['$refs'] & { diff --git a/src/components/form/b-input/b-input.ts b/src/components/form/b-input/b-input.ts index e930f9a693..9ebceea961 100644 --- a/src/components/form/b-input/b-input.ts +++ b/src/components/form/b-input/b-input.ts @@ -57,11 +57,9 @@ export default class bInput extends iInputText { declare readonly FormValue: FormValue; @prop({type: String, required: false}) - // @ts-ignore (override) override readonly valueProp?: this['Value']; @prop({type: String, required: false}) - // @ts-ignore (override) override readonly defaultProp?: this['Value']; /** @@ -229,7 +227,6 @@ export default class bInput extends iInputText { }; @system() - // @ts-ignore (override) protected override valueStore!: this['Value']; /** @@ -276,7 +273,6 @@ export default class bInput extends iInputText { }) }) - // @ts-ignore (override) protected override textStore!: string; @wait('ready', {label: $$.clear}) diff --git a/src/components/form/b-select-date/b-select-date.ts b/src/components/form/b-select-date/b-select-date.ts index 16adddff86..ffd63a6995 100644 --- a/src/components/form/b-select-date/b-select-date.ts +++ b/src/components/form/b-select-date/b-select-date.ts @@ -41,11 +41,9 @@ export default class bSelectDate extends iInput implements iWidth { declare readonly FormValue: FormValue; @prop({type: Date, required: false}) - // @ts-ignore (override) override readonly valueProp?: this['Value']; @prop({type: Date, required: false}) - // @ts-ignore (override) override readonly defaultProp?: this['Value']; /** diff --git a/src/components/form/b-textarea/b-textarea.ts b/src/components/form/b-textarea/b-textarea.ts index 40f0e4806d..f862504db1 100644 --- a/src/components/form/b-textarea/b-textarea.ts +++ b/src/components/form/b-textarea/b-textarea.ts @@ -61,11 +61,9 @@ export default class bTextarea extends iInputText { declare readonly FormValue: FormValue; @prop({type: String, required: false}) - // @ts-ignore (override) override readonly valueProp?: this['Value']; @prop({type: String, required: false}) - // @ts-ignore (override) override readonly defaultProp?: this['Value']; /** @@ -141,7 +139,6 @@ export default class bTextarea extends iInputText { }; @system() - // @ts-ignore (override) protected override valueStore!: this['Value']; @system({ @@ -180,7 +177,6 @@ export default class bTextarea extends iInputText { }) }) - // @ts-ignore (override) protected override textStore!: string; /** diff --git a/src/components/friends/module-loader/test/b-friends-module-loader-dummy/b-friends-module-loader-dummy.ts b/src/components/friends/module-loader/test/b-friends-module-loader-dummy/b-friends-module-loader-dummy.ts index 94aa151806..b30a5ee8aa 100644 --- a/src/components/friends/module-loader/test/b-friends-module-loader-dummy/b-friends-module-loader-dummy.ts +++ b/src/components/friends/module-loader/test/b-friends-module-loader-dummy/b-friends-module-loader-dummy.ts @@ -40,6 +40,5 @@ export default class bFriendsModuleLoaderDummy extends iData { [] }) - // @ts-ignore (override) override readonly dependenciesProp!: Module[]; } diff --git a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts index a6b858a092..1f7884f7b6 100644 --- a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts +++ b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts @@ -29,7 +29,6 @@ export default class pV4ComponentsDemo extends iStaticPage { readonly selfDispatchingProp: boolean = false; @system((o) => o.sync.link()) - // @ts-ignore (override) override readonly selfDispatching!: boolean; /** From 6f7512533a33ea343db69bb04bca897dce4be899 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 9 Sep 2024 15:50:09 +0300 Subject: [PATCH 114/334] chore: added doc --- src/core/component/field/store.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/component/field/store.ts b/src/core/component/field/store.ts index 168336967b..f0a289d79a 100644 --- a/src/core/component/field/store.ts +++ b/src/core/component/field/store.ts @@ -10,7 +10,9 @@ import { beforeHooks } from 'core/component/const'; import type { ComponentInterface } from 'core/component/interface'; /** - * Returns a reference to the storage object for the fields of the passed component + * Returns a reference to the storage object for the fields of the passed component. + * This method can be used to optimize access to the @field property instead of using `field.get`. + * * @param [component] */ export function getFieldsStore(component: ComponentInterface['unsafe']): object { From a75d2db4099bf7ad7a7d7e9afd826943e0d37882 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 9 Sep 2024 15:52:00 +0300 Subject: [PATCH 115/334] chore: componentOriginName -> componentFullName --- .../component/decorators/component/index.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 71c4a77923..ce24bcd58a 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -80,9 +80,9 @@ export function component(opts?: ComponentOptions): Function { isPartial = componentParams.partial != null; const - componentOriginName = componentInfo.name, + componentFullName = componentInfo.name, componentNormalizedName = componentInfo.componentName, - isParentLayerOverride = !isPartial && componentOriginName === componentInfo.parentParams?.name; + isParentLayerOverride = !isPartial && componentFullName === componentInfo.parentParams?.name; if (isParentLayerOverride) { Object.defineProperty(componentInfo.parent, OVERRIDDEN, {value: true}); @@ -97,15 +97,15 @@ export function component(opts?: ComponentOptions): Function { if (isPartial) { pushToInitList(() => { // Partial classes reuse the same metaobject - let meta = components.get(componentOriginName); + let meta = components.get(componentFullName); if (meta == null) { meta = createMeta(componentInfo); - components.set(componentOriginName, meta); + components.set(componentFullName, meta); } initEmitter.once(regEvent, () => { - addMethodsToMeta(components.get(componentOriginName)!, target); + addMethodsToMeta(components.get(componentFullName)!, target); }); }); @@ -117,13 +117,13 @@ export function component(opts?: ComponentOptions): Function { const needRegisterImmediate = componentInfo.isAbstract || componentParams.root === true || - !Object.isTruly(componentOriginName); + !Object.isTruly(componentFullName); if (needRegisterImmediate) { - registerComponent(componentOriginName); + registerComponent(componentFullName); } else { - requestIdleCallback(registerComponent.bind(null, componentOriginName)); + requestIdleCallback(registerComponent.bind(null, componentFullName)); } // If we have a smart component, @@ -131,7 +131,7 @@ export function component(opts?: ComponentOptions): Function { if (Object.isPlainObject(componentParams.functional)) { component({ ...opts, - name: `${componentOriginName}-functional`, + name: `${componentFullName}-functional`, functional: true })(target); } @@ -185,7 +185,7 @@ export function component(opts?: ComponentOptions): Function { } const meta = rawMeta!; - components.set(componentOriginName, meta); + components.set(componentFullName, meta); if (componentParams.name == null || !componentInfo.isSmart) { components.set(target, meta); @@ -209,13 +209,13 @@ export function component(opts?: ComponentOptions): Function { } } else if (meta.params.root) { - rootComponents[componentOriginName] = loadTemplate(getComponent(meta)); + rootComponents[componentFullName] = loadTemplate(getComponent(meta)); } else { - const componentDeclArgs = [componentOriginName, loadTemplate(getComponent(meta))]; + const componentDeclArgs = [componentFullName, loadTemplate(getComponent(meta))]; ComponentEngine.component(...componentDeclArgs); - if (app.context != null && app.context.component(componentOriginName) == null) { + if (app.context != null && app.context.component(componentFullName) == null) { app.context.component(...componentDeclArgs); } } @@ -258,8 +258,8 @@ export function component(opts?: ComponentOptions): Function { } function pushToInitList(init: Function) { - const initList = componentRegInitializers[componentOriginName] ?? []; - componentRegInitializers[componentOriginName] = initList; + const initList = componentRegInitializers[componentFullName] ?? []; + componentRegInitializers[componentFullName] = initList; initList.push(init); } }; From 66058fe241170b8428e057a15720470ebbd9cdc0 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 9 Sep 2024 18:36:22 +0300 Subject: [PATCH 116/334] fix: fixed default prop logic --- src/core/component/meta/fill.ts | 9 +++------ src/core/component/prop/init.ts | 19 ++++--------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 5d889a4bf0..e2b548df8b 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -128,14 +128,11 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor // it is necessary to clone this value for each new component instance // to ensure that they do not share the same value const needCloneDefValue = - defaultInstanceValue != null && typeof defaultInstanceValue === 'object' && - (!isTypeCanBeFunc(prop.type) || !Object.isFunction(defaultInstanceValue)); + !Object.isPrimitive(defaultInstanceValue) && + (prop.type !== Function || !Object.isFunction(defaultInstanceValue)); if (needCloneDefValue) { - getDefault = () => Object.isPrimitive(defaultInstanceValue) ? - defaultInstanceValue : - Object.fastClone(defaultInstanceValue); - + getDefault = () => Object.fastClone(defaultInstanceValue); (getDefault)[DEFAULT_WRAPPER] = true; } diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index fc04aec35c..2ba8478076 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -66,23 +66,12 @@ export function initProps( propValue = getAccessors()[0]; } - if (propValue === undefined) { - if (prop.default !== undefined) { - propValue = prop.default; - - } else { - propValue = meta.instance[propName]; - - if (!Object.isPrimitive(propValue)) { - propValue = Object.fastClone(propValue); - } - } - } - let needSaveToStore = opts.saveToStore; - if (Object.isFunction(propValue)) { - if (opts.saveToStore === true || propValue[DEFAULT_WRAPPER] !== true) { + if (propValue === undefined && prop.default !== undefined) { + propValue = prop.default; + + if (Object.isFunction(propValue) && (opts.saveToStore === true || propValue[DEFAULT_WRAPPER] !== true)) { propValue = prop.type === Function ? propValue : propValue(component); if (Object.isFunction(propValue)) { From 73b5aff48e7226c0360aa5dbabc59e95d9165de5 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 9 Sep 2024 18:48:41 +0300 Subject: [PATCH 117/334] chore: removed redundant logic --- src/core/component/meta/fill.ts | 13 +++++-------- src/core/component/prop/README.md | 10 ---------- src/core/component/prop/helpers.ts | 24 ------------------------ 3 files changed, 5 insertions(+), 42 deletions(-) diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index e2b548df8b..26be73be5a 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -10,8 +10,6 @@ import { DEFAULT_WRAPPER } from 'core/component/const'; import { getComponentContext } from 'core/component/context'; import { isAbstractComponent, isBinding } from 'core/component/reflect'; - -import { isTypeCanBeFunc } from 'core/component/prop'; import { addMethodsToMeta } from 'core/component/meta/method'; import type { ComponentConstructor, ComponentMeta, ModVal } from 'core/component/interface'; @@ -124,15 +122,14 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor let getDefault = defaultInstanceValue; - // If the default value of a field is set via default values for a class property, + // If the default value of a prop is set via a default value for a class property, // it is necessary to clone this value for each new component instance // to ensure that they do not share the same value - const needCloneDefValue = - !Object.isPrimitive(defaultInstanceValue) && - (prop.type !== Function || !Object.isFunction(defaultInstanceValue)); + if (prop.type !== Function && defaultInstanceValue != null && typeof defaultInstanceValue === 'object') { + getDefault = () => Object.isPrimitive(defaultInstanceValue) ? + defaultInstanceValue : + Object.fastClone(defaultInstanceValue); - if (needCloneDefValue) { - getDefault = () => Object.fastClone(defaultInstanceValue); (getDefault)[DEFAULT_WRAPPER] = true; } diff --git a/src/core/component/prop/README.md b/src/core/component/prop/README.md index d763882c57..69400231c3 100644 --- a/src/core/component/prop/README.md +++ b/src/core/component/prop/README.md @@ -9,13 +9,3 @@ This module offers an API to initialize component props within a component insta Initializes the input properties (also known as "props") for the given component instance. During the initialization of a component prop, its name will be stored in the `$activeField` property. The function returns a dictionary containing the initialized props. - -### isTypeCanBeFunc - -Returns true if the given prop type can be a function. - -```js -console.log(isTypeCanBeFunc(Boolean)); // false -console.log(isTypeCanBeFunc(Function)); // true -console.log(isTypeCanBeFunc([Function, Boolean])); // true -``` diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index 8c0ffe60d2..07b0da05e5 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -89,27 +89,3 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { } } } - -/** - * Returns true if the given prop type can be a function. - * - * @param type - * - * @example - * ```js - * console.log(isTypeCanBeFunc(Boolean)); // false - * console.log(isTypeCanBeFunc(Function)); // true - * console.log(isTypeCanBeFunc([Function, Boolean])); // true - * ``` - */ -export function isTypeCanBeFunc(type: CanUndef>): boolean { - if (!type) { - return false; - } - - if (Object.isArray(type)) { - return type.some((type) => type === Function); - } - - return type === Function; -} From b1bcce6b94179852fab252491b4bfe4726a9039b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 10 Sep 2024 13:39:05 +0300 Subject: [PATCH 118/334] fix: fixed issues with non-objects --- src/components/friends/field/delete.ts | 4 ++-- src/components/friends/field/get.ts | 30 ++++++++++++++------------ src/components/friends/field/set.ts | 4 ++-- src/core/component/reflect/property.ts | 2 +- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index aa48a721e1..ee7527795e 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -159,8 +159,8 @@ export function deleteField( prop = keyGetter ? keyGetter(chunks[0], ref) : chunks[0]; if (chunks.length > 1) { - chunks.some((key, i) => { - prop = keyGetter ? keyGetter(key, ref) : key; + chunks.some((chunk, i) => { + prop = keyGetter ? keyGetter(chunk, ref) : chunk; if (i + 1 === chunks.length) { return true; diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index ce1f692beb..9ede20adb8 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -126,40 +126,42 @@ export function getField( } if (chunks.length === 1) { - const key = chunks[0]; + const chunk = chunks[0]; if (getter != null) { - res = getter(key, res); + res = getter(chunk, res); } else { const obj = res; - res = key in obj ? obj[key] : undefined; + res = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; } } else { - const hasNoProperty = chunks.some((key) => { + const hasNoProperty = chunks.some((chunk) => { if (res == null) { return true; } - if (Object.isPromiseLike(res) && !(key in res)) { + if (Object.isPromiseLike(res) && !(chunk in res)) { res = res.then((res) => { if (getter != null) { - return getter(key, res); + return getter(chunk, res); + } + + if (res == null) { + return undefined; } const obj = res; - return key in obj ? obj[key] : undefined; + return typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; }); - } else { - if (getter != null) { - res = getter(key, res); + } else if (getter != null) { + res = getter(chunk, res); - } else { - const obj = res; - res = key in obj ? obj[key] : undefined; - } + } else { + const obj = res; + res = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; } return false; diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index d75b3c0c1b..e9a0ece3a4 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -160,8 +160,8 @@ export function setField( let prop = keyGetter ? keyGetter(chunks[0], ref) : chunks[0]; if (chunks.length > 1) { - chunks.some((key, i) => { - prop = keyGetter ? keyGetter(key, ref) : key; + chunks.some((chunk, i) => { + prop = keyGetter ? keyGetter(chunk, ref) : chunk; if (i + 1 === chunks.length) { return true; diff --git a/src/core/component/reflect/property.ts b/src/core/component/reflect/property.ts index 65e44369e4..6c658bf1f9 100644 --- a/src/core/component/reflect/property.ts +++ b/src/core/component/reflect/property.ts @@ -71,7 +71,7 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr return true; } - obj = chunk in obj ? obj[chunk] : undefined; + obj = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; if (obj != null && typeof obj === 'object' && V4_COMPONENT in obj) { component = obj; From 4494dca8f1d0114190b6a0be2027d117570e4da1 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 10 Sep 2024 14:40:00 +0300 Subject: [PATCH 119/334] chore: removed redundant Async contexts --- src/core/dom/intersection-watcher/engines/abstract.ts | 2 +- src/core/dom/resize-watcher/class.ts | 2 +- .../theme-manager/system-theme-extractor/engines/web/index.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/dom/intersection-watcher/engines/abstract.ts b/src/core/dom/intersection-watcher/engines/abstract.ts index 30e43fa9f0..c5f3fe5452 100644 --- a/src/core/dom/intersection-watcher/engines/abstract.ts +++ b/src/core/dom/intersection-watcher/engines/abstract.ts @@ -33,7 +33,7 @@ export default abstract class AbstractEngine { protected elements: ObservableElements = new Map(); /** {@link Async} */ - protected async: Async = new Async(this); + protected async: Async = new Async(); /** * Tracks the intersection of the passed element with the viewport, diff --git a/src/core/dom/resize-watcher/class.ts b/src/core/dom/resize-watcher/class.ts index cf8653dad8..5a7358e76d 100644 --- a/src/core/dom/resize-watcher/class.ts +++ b/src/core/dom/resize-watcher/class.ts @@ -27,7 +27,7 @@ export default class ResizeWatcher { protected elements: ObservableElements = new Map(); /** {@link Async} */ - protected async: Async = new Async(this); + protected async: Async = new Async(); constructor() { this.observer = new ResizeObserver((entries) => { diff --git a/src/core/theme-manager/system-theme-extractor/engines/web/index.ts b/src/core/theme-manager/system-theme-extractor/engines/web/index.ts index 52e1f8b838..4dbe3db48d 100644 --- a/src/core/theme-manager/system-theme-extractor/engines/web/index.ts +++ b/src/core/theme-manager/system-theme-extractor/engines/web/index.ts @@ -29,7 +29,7 @@ export class SystemThemeExtractorWeb implements SystemThemeExtractor { protected readonly emitter!: EventEmitterLikeP; /** {@link Async} */ - protected readonly async: Async = new Async(this); + protected readonly async: Async = new Async(); constructor() { if (SSR) { From e913e7937331e9604ea371faae21ddc3d12fc77c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 10 Sep 2024 17:00:03 +0300 Subject: [PATCH 120/334] fix: fixed an issues with deactivation --- src/components/super/i-block/state/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 1d170db74c..b92451da74 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -195,6 +195,7 @@ export default abstract class iBlockState extends iBlockMods { this.shadowComponentStatusStore = value; } else { + this.shadowComponentStatusStore = undefined; this.field.getFieldsStore().componentStatusStore = value; if (this.isReady && this.dependencies.length > 0) { From d56e992cfccba14559fd6a97856377459074360f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 10 Sep 2024 18:06:13 +0300 Subject: [PATCH 121/334] fix: fixes issues with maps --- src/components/friends/field/delete.ts | 6 ++++-- src/components/friends/field/get.ts | 21 +++++++++++++++++++-- src/components/friends/field/set.ts | 23 ++++++++++++++++++++--- src/core/component/reflect/property.ts | 7 ++++++- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index ee7527795e..9f6d46e71b 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -166,8 +166,7 @@ export function deleteField( return true; } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - const newRef = ref != null ? ref[prop] : undefined; + const newRef = Object.isMap(ref) ? ref.get(prop) : ref[prop]; if (newRef == null || typeof newRef !== 'object') { needDelete = false; @@ -184,6 +183,9 @@ export function deleteField( if (needDeleteToWatch) { ctx.$delete(ref, prop); + } else if (Object.isMap(ref)) { + ref.delete(prop); + } else { delete ref[prop]; } diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index 9ede20adb8..cfd6c68e5e 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -133,7 +133,13 @@ export function getField( } else { const obj = res; - res = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; + + if (Object.isMap(obj)) { + res = obj.get(chunk); + + } else { + res = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; + } } } else { @@ -153,6 +159,11 @@ export function getField( } const obj = res; + + if (Object.isMap(obj)) { + return obj.get(chunk); + } + return typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; }); @@ -161,7 +172,13 @@ export function getField( } else { const obj = res; - res = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; + + if (Object.isMap(obj)) { + res = obj.get(chunk); + + } else { + res = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; + } } return false; diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index e9a0ece3a4..a8bf3a1b67 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -176,12 +176,21 @@ export function setField( if (needSetToWatch) { ctx.$set(ref, prop, newRef); + } else if (Object.isMap(ref)) { + ref.set(prop, newRef); + } else { ref[prop] = newRef; } } - ref = ref[prop]; + if (Object.isMap(ref)) { + ref = Object.cast(ref.get(prop)); + + } else { + ref = ref[prop]; + } + return false; }); } @@ -191,8 +200,16 @@ export function setField( return value; } - if (!needSetToWatch || !Object.isArray(ref) && prop in ref) { - ref[prop] = value; + if (Object.isMap(ref)) { + if (!needSetToWatch || ref.has(prop)) { + ref.set(prop, value); + + } else { + ctx.$set(ref, prop, value); + } + + } else if (!needSetToWatch || !Object.isArray(ref) && prop in ref) { + ref[prop] = value; } else { ctx.$set(ref, prop, value); diff --git a/src/core/component/reflect/property.ts b/src/core/component/reflect/property.ts index 6c658bf1f9..da62d366cc 100644 --- a/src/core/component/reflect/property.ts +++ b/src/core/component/reflect/property.ts @@ -71,7 +71,12 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr return true; } - obj = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; + if (Object.isMap(obj)) { + obj = Object.cast(obj.get(chunk)); + + } else { + obj = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; + } if (obj != null && typeof obj === 'object' && V4_COMPONENT in obj) { component = obj; From c0864f406bd6d19f69bfc1cdc3f551ca06f22a39 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 12 Sep 2024 15:46:46 +0300 Subject: [PATCH 122/334] fix: fixed an issues with Map --- src/components/friends/field/set.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index a8bf3a1b67..75b3350672 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -167,8 +167,11 @@ export function setField( return true; } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - let newRef = ref != null ? ref[prop] : undefined; + type AnyMap = Map; + + const isRefMap = Object.isMap(ref); + + let newRef: unknown = isRefMap ? (ref).get(prop) : ref[prop]; if (newRef == null || typeof newRef !== 'object') { newRef = isNaN(Number(chunks[i + 1])) ? {} : []; @@ -176,16 +179,16 @@ export function setField( if (needSetToWatch) { ctx.$set(ref, prop, newRef); - } else if (Object.isMap(ref)) { - ref.set(prop, newRef); + } else if (isRefMap) { + (ref).set(prop, newRef); } else { ref[prop] = newRef; } } - if (Object.isMap(ref)) { - ref = Object.cast(ref.get(prop)); + if (isRefMap) { + ref = (ref).get(prop); } else { ref = ref[prop]; From 21e6c93a71eeecdbaa5b7cdf9531c443baecc838 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 12 Sep 2024 17:30:34 +0300 Subject: [PATCH 123/334] fix: props must be initialized after `beforeRuntime` --- src/core/component/engines/vue3/component.ts | 2 +- src/core/component/functional/context/create.ts | 12 ++++++++---- src/core/component/init/states/before-create.ts | 17 ++++++++++------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index 5c25a1dc72..eff52f3029 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -82,7 +82,7 @@ export function getComponent(meta: ComponentMeta): ComponentOptions { if (once) { diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 56f7176ea6..7bac938b4b 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -37,7 +37,8 @@ const $getParent = Symbol('$getParent'); /** - * Initializes the "beforeCreate" state to the specified component instance + * Initializes the "beforeCreate" state to the specified component instance. + * The function returns a function for transitioning to the beforeCreate hook. * * @param component * @param meta - the component metaobject @@ -47,9 +48,11 @@ export function beforeCreateState( component: ComponentInterface, meta: ComponentMeta, opts?: InitBeforeCreateStateOptions -): void { +): () => void { meta = forkMeta(meta); + const isFunctional = meta.params.functional === true; + // To avoid TS errors marks all properties as editable const unsafe = Object.cast>(component); unsafe[V4_COMPONENT] = true; @@ -301,9 +304,7 @@ export function beforeCreateState( const fakeHandler = () => undefined; if (watchDependencies.size > 0) { - const - isFunctional = meta.params.functional === true, - watchSet = new Set(); + const watchSet = new Set(); watchDependencies.forEach((deps) => { deps.forEach((dep) => { @@ -368,6 +369,8 @@ export function beforeCreateState( } }); - runHook('beforeCreate', component).catch(stderr); - callMethodFromComponent(component, 'beforeCreate'); + return () => { + runHook('beforeCreate', component).catch(stderr); + callMethodFromComponent(component, 'beforeCreate'); + }; } From 7eb45d097ef30af802c26983edcf70515738083d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 12 Sep 2024 17:32:30 +0300 Subject: [PATCH 124/334] fix: fixed errors during synchronization --- src/components/friends/sync/link.ts | 6 +++--- src/components/friends/sync/mod.ts | 20 ++++++++++++-------- src/components/traits/i-visible/i-visible.ts | 11 ++++------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 7caae3c9b1..c5c1f1e328 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -487,11 +487,11 @@ export function link( } const initSync = () => { - const path: string = needCollapse ? info.originalTopPath : info.originalPath; + const {path} = info; // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (info.path.includes('.')) { - return sync(this.field.get(path)); + if (path.includes('.')) { + return sync(this.field.get(needCollapse ? info.originalTopPath : info.originalPath)); } return sync(info.type === 'field' ? this.field.getFieldsStore(info.ctx)[path] : info.ctx[path]); diff --git a/src/components/friends/sync/mod.ts b/src/components/friends/sync/mod.ts index 00cba8bed7..1dfeffd3dd 100644 --- a/src/components/friends/sync/mod.ts +++ b/src/components/friends/sync/mod.ts @@ -91,6 +91,7 @@ export function mod( if (Object.isFunction(optsOrConverter)) { converter = optsOrConverter; + opts = {}; } else { opts = Object.cast(optsOrConverter); @@ -100,7 +101,8 @@ export function mod( const that = this, - info = getPropertyInfo(path, this.component); + info = getPropertyInfo(path, this.component), + needCollapse = opts.collapse !== false; if (this.lfc.isBeforeCreate()) { this.syncModCache[modName] = sync; @@ -121,19 +123,21 @@ export function mod( } function sync() { - let v: unknown; + const {path} = info; - if (info.path.includes('.')) { - v = that.field.get(path); + let val: unknown; + + if (path.includes('.')) { + val = that.field.get(needCollapse ? info.originalTopPath : info.originalPath); } else { - v = info.type === 'field' ? that.field.getFieldsStore(info.ctx)[path] : info.ctx[path]; + val = info.type === 'field' ? that.field.getFieldsStore(info.ctx)[path] : info.ctx[path]; } - v = (converter).call(that.component, v); + val = (converter).call(that.component, val); - if (v !== undefined) { - ctx.mods[modName] = String(v); + if (val !== undefined) { + ctx.mods[modName] = String(val); } } diff --git a/src/components/traits/i-visible/i-visible.ts b/src/components/traits/i-visible/i-visible.ts index 5da1c68474..c1fcac60aa 100644 --- a/src/components/traits/i-visible/i-visible.ts +++ b/src/components/traits/i-visible/i-visible.ts @@ -40,24 +40,21 @@ export default abstract class iVisible { * @param component */ static initModEvents(component: T): void { - const { - $el, - localEmitter: $e - } = component.unsafe; + const {unsafe} = component; component.sync.mod('hidden', 'r.isOnline', (v) => component.hideIfOffline && v === false); - $e.on('block.mod.*.hidden.*', (e: ModEvent) => { + unsafe.localEmitter.on('block.mod.*.hidden.*', (e: ModEvent) => { if (e.type === 'remove' && e.reason !== 'removeMod') { return; } if (e.value === 'false' || e.type === 'remove') { - $el?.setAttribute('aria-hidden', 'true'); + unsafe.$el?.setAttribute('aria-hidden', 'true'); component.emit('show'); } else { - $el?.setAttribute('aria-hidden', 'false'); + unsafe.$el?.setAttribute('aria-hidden', 'false'); component.emit('hide'); } }); From 06676f492502a11097905debedb153629cf3af47 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 12 Sep 2024 17:54:29 +0300 Subject: [PATCH 125/334] chore: fixed eslint --- src/core/component/functional/context/create.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/component/functional/context/create.ts b/src/core/component/functional/context/create.ts index eb54d9de15..10103f653a 100644 --- a/src/core/component/functional/context/create.ts +++ b/src/core/component/functional/context/create.ts @@ -10,8 +10,6 @@ import * as init from 'core/component/init'; import { saveRawComponentContext } from 'core/component/context'; import { forkMeta, ComponentMeta } from 'core/component/meta'; - -import { runHook } from 'core/component/hook'; import { initProps } from 'core/component/prop'; import type { ComponentInterface } from 'core/component/interface'; From 365849f4b5f7f94bf81d48d9698ff0b080be199b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 12 Sep 2024 18:15:33 +0300 Subject: [PATCH 126/334] fix: reverted --- src/components/friends/sync/mod.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/friends/sync/mod.ts b/src/components/friends/sync/mod.ts index 1dfeffd3dd..e8b41d4e72 100644 --- a/src/components/friends/sync/mod.ts +++ b/src/components/friends/sync/mod.ts @@ -101,8 +101,7 @@ export function mod( const that = this, - info = getPropertyInfo(path, this.component), - needCollapse = opts.collapse !== false; + info = getPropertyInfo(path, this.component); if (this.lfc.isBeforeCreate()) { this.syncModCache[modName] = sync; @@ -128,7 +127,7 @@ export function mod( let val: unknown; if (path.includes('.')) { - val = that.field.get(needCollapse ? info.originalTopPath : info.originalPath); + val = that.field.get(info.originalPath); } else { val = info.type === 'field' ? that.field.getFieldsStore(info.ctx)[path] : info.ctx[path]; From 26c2bec0eb1ef1a83be0ae9a62a9e747acd93f28 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 12 Sep 2024 18:54:50 +0300 Subject: [PATCH 127/334] fix: reverted --- src/components/traits/i-visible/i-visible.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/traits/i-visible/i-visible.ts b/src/components/traits/i-visible/i-visible.ts index c1fcac60aa..67ed586a7e 100644 --- a/src/components/traits/i-visible/i-visible.ts +++ b/src/components/traits/i-visible/i-visible.ts @@ -40,21 +40,21 @@ export default abstract class iVisible { * @param component */ static initModEvents(component: T): void { - const {unsafe} = component; + const {$el, localEmitter: $e} = component.unsafe; component.sync.mod('hidden', 'r.isOnline', (v) => component.hideIfOffline && v === false); - unsafe.localEmitter.on('block.mod.*.hidden.*', (e: ModEvent) => { + $e.on('block.mod.*.hidden.*', (e: ModEvent) => { if (e.type === 'remove' && e.reason !== 'removeMod') { return; } if (e.value === 'false' || e.type === 'remove') { - unsafe.$el?.setAttribute('aria-hidden', 'true'); + $el?.setAttribute('aria-hidden', 'true'); component.emit('show'); } else { - unsafe.$el?.setAttribute('aria-hidden', 'false'); + $el?.setAttribute('aria-hidden', 'false'); component.emit('hide'); } }); From c4425394ebe20b58ed1caa9cf323991662860e1b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 13 Sep 2024 13:52:04 +0300 Subject: [PATCH 128/334] fix: reverted --- src/core/component/engines/vue3/component.ts | 2 +- src/core/component/functional/context/create.ts | 10 ++++------ src/core/component/init/states/before-create.ts | 8 +++----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index eff52f3029..5c25a1dc72 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -82,7 +82,7 @@ export function getComponent(meta: ComponentMeta): ComponentOptions { if (once) { diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 7bac938b4b..d88aa62b84 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -48,7 +48,7 @@ export function beforeCreateState( component: ComponentInterface, meta: ComponentMeta, opts?: InitBeforeCreateStateOptions -): () => void { +): void { meta = forkMeta(meta); const isFunctional = meta.params.functional === true; @@ -369,8 +369,6 @@ export function beforeCreateState( } }); - return () => { - runHook('beforeCreate', component).catch(stderr); - callMethodFromComponent(component, 'beforeCreate'); - }; + runHook('beforeCreate', component).catch(stderr); + callMethodFromComponent(component, 'beforeCreate'); } From ba65d313c0bb313c787cef730a9d1f97257f14e6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 13 Sep 2024 15:25:45 +0300 Subject: [PATCH 129/334] feat: added support for property descriptors --- src/components/friends/field/class.ts | 15 ++++++++------- src/components/friends/field/delete.ts | 19 ++++++++++++------- src/components/friends/field/get.ts | 20 +++++++++++++------- src/components/friends/field/set.ts | 19 ++++++++++++------- 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/components/friends/field/class.ts b/src/components/friends/field/class.ts index cacf548bf7..3d8e2f5fa8 100644 --- a/src/components/friends/field/class.ts +++ b/src/components/friends/field/class.ts @@ -6,7 +6,8 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { getFieldsStore, ComponentInterface } from 'core/component'; +import { getFieldsStore, ComponentInterface, PropertyInfo } from 'core/component'; + import Friend, { fakeMethods } from 'components/friends/friend'; import { getField } from 'components/friends/field/get'; @@ -19,14 +20,14 @@ import('components/friends/field/test/b-friends-field-dummy'); //#endif interface Field { - get(path: string, getter: ValueGetter): CanUndef; - get(path: string, obj?: Nullable, getter?: ValueGetter): CanUndef; + get(path: string | PropertyInfo, getter: ValueGetter): CanUndef; + get(path: string | PropertyInfo, obj?: Nullable, getter?: ValueGetter): CanUndef; - set(path: string, value: T, keyGetter: KeyGetter): T; - set(path: string, value: T, obj?: Nullable, keyGetter?: KeyGetter): T; + set(path: string | PropertyInfo, value: T, keyGetter: KeyGetter): T; + set(path: string | PropertyInfo, value: T, obj?: Nullable, keyGetter?: KeyGetter): T; - delete(path: string, keyGetter?: KeyGetter): boolean; - delete(path: string, obj?: Nullable, keyGetter?: KeyGetter): boolean; + delete(path: string | PropertyInfo, keyGetter?: KeyGetter): boolean; + delete(path: string | PropertyInfo, obj?: Nullable, keyGetter?: KeyGetter): boolean; } @fakeMethods('delete') diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index 9f6d46e71b..8607836f10 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -7,16 +7,17 @@ */ import { unwrap } from 'core/object/watch'; +import { getPropertyInfo, V4_COMPONENT, PropertyInfo } from 'core/component'; import type Friend from 'components/friends/friend'; -import iBlock, { getPropertyInfo, V4_COMPONENT } from 'components/super/i-block/i-block'; +import type iBlock from 'components/super/i-block/i-block'; import type { KeyGetter } from 'components/friends/field/interface'; /** * Deletes a component property at the specified path * - * @param path - the property path, for instance `foo.bla.bar` + * @param path - the property path, for instance `foo.bla.bar`, or a property descriptor * @param keyGetter - a function that returns the key to delete * * @example @@ -41,12 +42,12 @@ import type { KeyGetter } from 'components/friends/field/interface'; * } * ``` */ -export function deleteField(this: Friend, path: string, keyGetter?: KeyGetter): boolean; +export function deleteField(this: Friend, path: string | PropertyInfo, keyGetter?: KeyGetter): boolean; /** * Deletes a property from the passed object at the specified path * - * @param path - the property path, for instance `foo.bla.bar` + * @param path - the property path, for instance `foo.bla.bar`, or a property descriptor * @param [obj] - the object to delete the property * @param [keyGetter] - a function that returns the key to delete * @@ -66,14 +67,14 @@ export function deleteField(this: Friend, path: string, keyGetter?: KeyGetter): */ export function deleteField( this: Friend, - path: string, + path: string | PropertyInfo, obj?: Nullable, keyGetter?: KeyGetter ): boolean; export function deleteField( this: Friend, - path: string, + path: string | PropertyInfo, obj: Nullable = this.ctx, keyGetter?: KeyGetter ): boolean { @@ -104,7 +105,7 @@ export function deleteField( chunks: string[]; if (isComponent) { - const info = getPropertyInfo(path, Object.cast(ctx)); + const info = Object.isString(path) ? getPropertyInfo(path, Object.cast(ctx)) : path; const isReady = !ctx.lfc.isBeforeCreate(), @@ -151,6 +152,10 @@ export function deleteField( } } else { + if (!Object.isString(path)) { + path = path.originalPath; + } + chunks = path.includes('.') ? path.split('.') : [path]; } diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index cfd6c68e5e..49a8eb7ff1 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -6,15 +6,17 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import { getPropertyInfo, V4_COMPONENT, PropertyInfo } from 'core/component'; + import type Field from 'components/friends/field'; -import iBlock, { getPropertyInfo, V4_COMPONENT } from 'components/super/i-block/i-block'; +import type iBlock from 'components/super/i-block/i-block'; import type { ValueGetter } from 'components/friends/field/interface'; /** * Returns a component property at the specified path * - * @param path - the property path, for instance `foo.bla.bar` + * @param path - the property path, for instance `foo.bla.bar`, or a property descriptor * @param getter - a function used to get a value from an object and a property * * @example @@ -39,12 +41,12 @@ import type { ValueGetter } from 'components/friends/field/interface'; * } * ``` */ -export function getField(this: Field, path: string, getter: ValueGetter): CanUndef; +export function getField(this: Field, path: string | PropertyInfo, getter: ValueGetter): CanUndef; /** * Returns a property from the passed object at the specified path * - * @param path - the property path, for instance `foo.bla.bar` + * @param path - the property path, for instance `foo.bla.bar`, or a property descriptor * @param [obj] - the object to search * @param [getter] - a function that is used to get a value from an object and a property * @@ -59,14 +61,14 @@ export function getField(this: Field, path: string, getter: ValueGe */ export function getField( this: Field, - path: string, + path: string | PropertyInfo, obj?: Nullable, getter?: ValueGetter ): CanUndef; export function getField( this: Field, - path: string, + path: string | PropertyInfo, obj: Nullable = this.ctx, getter?: ValueGetter ): CanUndef { @@ -93,7 +95,7 @@ export function getField( chunks: string[]; if (isComponent) { - const info = getPropertyInfo(path, Object.cast(ctx)); + const info = Object.isString(path) ? getPropertyInfo(path, Object.cast(ctx)) : path; ctx = Object.cast(info.ctx); res = ctx; @@ -122,6 +124,10 @@ export function getField( } } else { + if (!Object.isString(path)) { + path = path.originalPath; + } + chunks = path.includes('.') ? path.split('.') : [path]; } diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index 75b3350672..dc81054b7f 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -7,16 +7,17 @@ */ import { unwrap } from 'core/object/watch'; +import { getPropertyInfo, V4_COMPONENT, PropertyInfo } from 'core/component'; import type Friend from 'components/friends/friend'; -import iBlock, { getPropertyInfo, V4_COMPONENT } from 'components/super/i-block/i-block'; +import type iBlock from 'components/super/i-block/i-block'; import type { KeyGetter } from 'components/friends/field/interface'; /** * Sets a new component property at the specified path * - * @param path - the property path, for instance `foo.bla.bar` + * @param path - the property path, for instance `foo.bla.bar`, or a property descriptor * @param value - the value to set to the property * @param keyGetter - a function that returns the key to set * @@ -39,12 +40,12 @@ import type { KeyGetter } from 'components/friends/field/interface'; * } * ``` */ -export function setField(this: Friend, path: string, value: T, keyGetter: KeyGetter): T; +export function setField(this: Friend, path: string | PropertyInfo, value: T, keyGetter: KeyGetter): T; /** * Sets a new property on the passed object at the specified path * - * @param path - the property path, for instance `foo.bla.bar` + * @param path - the property path, for instance `foo.bla.bar`, or a property descriptor * @param value - the value to set to the property * @param [obj] - the object to set the property * @param [keyGetter] - a function that returns the key to set @@ -62,7 +63,7 @@ export function setField(this: Friend, path: string, value: T, keyG */ export function setField( this: Friend, - path: string, + path: string | PropertyInfo, value: T, obj?: Nullable, keyGetter?: KeyGetter @@ -70,7 +71,7 @@ export function setField( export function setField( this: Friend, - path: string, + path: string | PropertyInfo, value: T, obj: Nullable = this.ctx, keyGetter?: KeyGetter @@ -102,7 +103,7 @@ export function setField( chunks: string[]; if (isComponent) { - const info = getPropertyInfo(path, Object.cast(ctx)); + const info = Object.isString(path) ? getPropertyInfo(path, Object.cast(ctx)) : path; ctx = Object.cast(info.ctx); ref = ctx; @@ -154,6 +155,10 @@ export function setField( } } else { + if (!Object.isString(path)) { + path = path.originalPath; + } + chunks = path.includes('.') ? path.split('.') : [path]; } From 873d434ff26f9e490a9fade16f15038e4fcdcf63 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 13 Sep 2024 15:25:55 +0300 Subject: [PATCH 130/334] fix: fixes bugs --- src/components/friends/sync/link.ts | 58 ++++++++++++----------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index c5c1f1e328..2d3d5f30e3 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -7,7 +7,7 @@ */ import { isProxy } from 'core/object/watch'; -import { getPropertyInfo, isBinding, isCustomWatcher } from 'core/component'; +import { getPropertyInfo, isBinding, isCustomWatcher, PropertyInfo } from 'core/component'; import type iBlock from 'components/super/i-block/i-block'; import type Sync from 'components/friends/sync/class'; @@ -310,7 +310,7 @@ export function link( } let - info, + srcInfo, normalizedPath: CanUndef, topPathIndex = 1; @@ -325,12 +325,12 @@ export function link( customWatcher = true; } else { - info = getPropertyInfo(normalizedPath, this.component); + srcInfo = getPropertyInfo(normalizedPath, this.component); - if (info.type === 'mounted') { + if (srcInfo.type === 'mounted') { mountedWatcher = true; - normalizedPath = info.path; - topPathIndex = Object.size(info.path) > 0 ? 0 : 1; + normalizedPath = srcInfo.path; + topPathIndex = Object.size(srcInfo.path) > 0 ? 0 : 1; } } @@ -338,18 +338,18 @@ export function link( mountedWatcher = true; if (isProxy(resolvedPath)) { - info = {ctx: resolvedPath}; + srcInfo = {ctx: resolvedPath}; normalizedPath = undefined; } else { - info = resolvedPath; - normalizedPath = info.path; + srcInfo = resolvedPath; + normalizedPath = srcInfo.path; topPathIndex = 0; } } - const isAccessor = info != null ? - Boolean(info.type === 'accessor' || info.type === 'computed' || info.accessor) : + const isAccessor = srcInfo != null ? + Boolean(srcInfo.type === 'accessor' || srcInfo.type === 'computed' || srcInfo.accessor) : false; if (isAccessor) { @@ -375,6 +375,8 @@ export function link( linksCache[destPath] = {}; + let destInfo: CanUndef; + const sync = (val?: unknown, oldVal?: unknown) => { const resolveVal = getter ? getter.call(this.component, val, oldVal) : val; @@ -382,28 +384,14 @@ export function link( return resolveVal; } - const info = getPropertyInfo(destPath, this.component); - - if (info.path.includes('.') || info.type === 'mounted') { - this.field.set(destPath, resolveVal); - - } else if (info.type === 'field') { - const store = this.field.getFieldsStore(info.ctx); - store[destPath] = resolveVal; - - if (store !== info.ctx) { - info.ctx[destPath] = resolveVal; - } - - } else { - info.ctx[destPath] = resolveVal; - } + destInfo ??= getPropertyInfo(destPath, this.component); + this.field.set(destInfo, resolveVal); return resolveVal; }; if (getter != null && (getter.length > 1 || getter['originalLength'] > 1)) { - ctx.watch(info ?? normalizedPath, resolvedOpts, (val: unknown, oldVal: unknown, ...args: unknown[]) => { + ctx.watch(srcInfo ?? normalizedPath, resolvedOpts, (val: unknown, oldVal: unknown, ...args: unknown[]) => { if (customWatcher) { oldVal = undefined; @@ -424,7 +412,7 @@ export function link( }); } else { - ctx.watch(info ?? normalizedPath, resolvedOpts, (val: unknown, ...args: unknown[]) => { + ctx.watch(srcInfo ?? normalizedPath, resolvedOpts, (val: unknown, ...args: unknown[]) => { let oldVal: unknown = undefined; @@ -453,8 +441,8 @@ export function link( let key: Nullable; if (mountedWatcher) { - const o = info?.originalPath; - key = Object.isString(o) ? o : info?.ctx ?? normalizedPath; + const o = srcInfo?.originalPath; + key = Object.isString(o) ? o : srcInfo?.ctx ?? normalizedPath; } else { key = normalizedPath; @@ -477,7 +465,7 @@ export function link( const needCollapse = resolvedOpts.collapse !== false; if (mountedWatcher) { - const obj = info?.ctx; + const obj = srcInfo?.ctx; if (needCollapse || normalizedPath == null || normalizedPath.length === 0) { return sync(obj); @@ -487,14 +475,14 @@ export function link( } const initSync = () => { - const {path} = info; + const {path} = srcInfo; // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (path.includes('.')) { - return sync(this.field.get(needCollapse ? info.originalTopPath : info.originalPath)); + return sync(this.field.get(needCollapse ? srcInfo.originalTopPath : srcInfo.originalPath)); } - return sync(info.type === 'field' ? this.field.getFieldsStore(info.ctx)[path] : info.ctx[path]); + return sync(srcInfo.type === 'field' ? this.field.getFieldsStore(srcInfo.ctx)[path] : srcInfo.ctx[path]); }; if (this.lfc.isBeforeCreate('beforeDataCreate')) { From 29277c4ec349d73b0729ec1bd51ec2c4a7430f76 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 13 Sep 2024 15:49:15 +0300 Subject: [PATCH 131/334] chore: speedup --- src/core/component/watch/component-api.ts | 52 +++++++++++------------ 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/core/component/watch/component-api.ts b/src/core/component/watch/component-api.ts index c3feeee4a0..c6bfae70c3 100644 --- a/src/core/component/watch/component-api.ts +++ b/src/core/component/watch/component-api.ts @@ -47,7 +47,7 @@ import type { ComponentInterface, RawWatchHandler } from 'core/component/interfa export function implementComponentWatchAPI(component: ComponentInterface): void { const { unsafe, - unsafe: {$async: $a, meta: {computedFields, watchDependencies, watchPropDependencies, params}}, + unsafe: {$destructors, meta: {computedFields, watchDependencies, watchPropDependencies, params}}, $renderEngine: {proxyGetters} } = component; @@ -55,8 +55,7 @@ export function implementComponentWatchAPI(component: ComponentInterface): void isFunctional = SSR || params.functional === true, usedHandlers = new Set(); - let - timerId; + let timerId: CanUndef>; const fieldsInfo = proxyGetters.field(component), @@ -168,8 +167,7 @@ export function implementComponentWatchAPI(component: ComponentInterface): void // Watcher of fields - let - fieldsWatcher; + let fieldsWatcher; if (isFunctional) { // Don't force watching of fields until it becomes necessary @@ -197,17 +195,16 @@ export function implementComponentWatchAPI(component: ComponentInterface): void createComputedCacheInvalidator() ); - $a.worker(() => systemFieldsWatcher.unwatch()); - - { - const w = watch( - systemFieldsWatcher.proxy, - watchOpts, - createAccessorMutationEmitter() - ); + const accessorsWatcher = watch( + systemFieldsWatcher.proxy, + watchOpts, + createAccessorMutationEmitter() + ); - $a.worker(() => w.unwatch()); - } + $destructors.push(() => { + systemFieldsWatcher.unwatch(); + accessorsWatcher.unwatch(); + }); initWatcher(systemFieldsInfo.key, systemFieldsWatcher); }; @@ -259,7 +256,7 @@ export function implementComponentWatchAPI(component: ComponentInterface): void // we need to wrap a prop object by myself if (!('watch' in props)) { const propsWatcher = watch(propsStore, propWatchOpts); - $a.worker(() => propsWatcher.unwatch()); + $destructors.push(() => propsWatcher.unwatch()); initWatcher(props.key, propsWatcher); } @@ -303,11 +300,11 @@ export function implementComponentWatchAPI(component: ComponentInterface): void if (isFunctional) { // We need to track all modified fields of the functional instance // to restore state if a parent has re-created the component - const w = watch(watcher.proxy, {deep: true, collapse: true, immediate: true}, (v, o, i) => { + const w = watch(watcher.proxy, {deep: true, collapse: true, immediate: true}, (_v, _o, i) => { unsafe.$modifiedFields[String(i.path[0])] = true; }); - $a.worker(() => w.unwatch()); + $destructors.push(() => w.unwatch()); } } @@ -323,17 +320,16 @@ export function implementComponentWatchAPI(component: ComponentInterface): void createComputedCacheInvalidator() ); - $a.worker(() => fieldsWatcher.unwatch()); - - { - const w = watch( - fieldsWatcher.proxy, - watchOpts, - createAccessorMutationEmitter() - ); + const accessorsWatcher = watch( + fieldsWatcher.proxy, + watchOpts, + createAccessorMutationEmitter() + ); - $a.worker(() => w.unwatch()); - } + $destructors.push(() => { + fieldsWatcher.unwatch(); + accessorsWatcher.unwatch(); + }); initWatcher(fieldsInfo.key, fieldsWatcher); } From cb15e3951a574d23a6f67606c86e1e94cc5c1124 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 13 Sep 2024 16:03:10 +0300 Subject: [PATCH 132/334] fix: fixed isComponent check --- src/components/friends/field/delete.ts | 2 +- src/components/friends/field/get.ts | 2 +- src/components/friends/field/set.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index 8607836f10..5eff50ffe1 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -91,7 +91,7 @@ export function deleteField( let isComponent = false; - if (V4_COMPONENT in obj) { + if (typeof obj === 'object' && V4_COMPONENT in obj) { ctx = (obj).unsafe; isComponent = true; } diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index 49a8eb7ff1..aabde4de90 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -85,7 +85,7 @@ export function getField( let isComponent = false; - if (V4_COMPONENT in obj) { + if (typeof obj === 'object' && V4_COMPONENT in obj) { ctx = (obj).unsafe; isComponent = true; } diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index dc81054b7f..7ed6fe5770 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -89,7 +89,7 @@ export function setField( let isComponent = false; - if (V4_COMPONENT in obj) { + if (typeof obj === 'object' && V4_COMPONENT in obj) { ctx = (obj).unsafe; isComponent = true; } From d73dfacb0fa9447c7558db0c982647c72a44fd77 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 16 Sep 2024 15:03:21 +0300 Subject: [PATCH 133/334] refactor: use getters for friendly classes --- .../b-virtual-scroll-new.ts | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts index 675132766f..cd3dca7998 100644 --- a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts +++ b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts @@ -55,7 +55,19 @@ import { ComponentFactory } from 'components/base/b-virtual-scroll-new/modules/f import { Observer } from 'components/base/b-virtual-scroll-new/modules/observer'; import { isAsyncReplaceError } from 'components/base/b-virtual-scroll-new/modules/helpers'; -import iData, { component, system, field, watch, wait, RequestParams, UnsafeGetter } from 'components/super/i-data/i-data'; +import iData, { + + component, + field, + computed, + + watch, + wait, + + RequestParams, + UnsafeGetter + +} from 'components/super/i-data/i-data'; export * from 'components/base/b-virtual-scroll-new/interface'; export * from 'components/base/b-virtual-scroll-new/const'; @@ -72,24 +84,34 @@ interface bVirtualScrollNew extends Trait {} @derive(iVirtualScrollHandlers) class bVirtualScrollNew extends iVirtualScrollProps implements iItems { /** {@link componentTypedEmitter} */ - @system((ctx) => componentTypedEmitter(ctx)) - protected readonly componentEmitter!: ComponentTypedEmitter; + @computed({cache: 'forever'}) + protected get componentEmitter(): ComponentTypedEmitter { + return componentTypedEmitter(this.unsafe); + } /** {@link SlotsStateController} */ - @system((ctx) => new SlotsStateController(ctx)) - protected readonly slotsStateController!: SlotsStateController; + @computed({cache: 'forever'}) + protected get slotsStateController(): SlotsStateController { + return new SlotsStateController(this); + } /** {@link ComponentInternalState} */ - @system((ctx) => new ComponentInternalState(ctx)) - protected readonly componentInternalState!: ComponentInternalState; + @computed({cache: 'forever'}) + protected get componentInternalState(): ComponentInternalState { + return new ComponentInternalState(this); + } /** {@link ComponentFactory} */ - @system((ctx) => new ComponentFactory(ctx)) - protected readonly componentFactory!: ComponentFactory; + @computed({cache: 'forever'}) + protected get componentFactory(): ComponentFactory { + return new ComponentFactory(this); + } /** {@link Observer} */ - @system((ctx) => new Observer(ctx)) - protected readonly observer!: Observer; + @computed({cache: 'forever'}) + protected get observer(): Observer { + return new Observer(this); + } /** @inheritDoc */ declare protected readonly $refs: iData['$refs'] & $ComponentRefs; From df6111a80fe71c8f396caf821d96f3dfe91df782 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 16 Sep 2024 15:27:30 +0300 Subject: [PATCH 134/334] refactor: better structure --- src/core/component/field/init.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/component/field/init.ts b/src/core/component/field/init.ts index 059255293a..567cc03d4f 100644 --- a/src/core/component/field/init.ts +++ b/src/core/component/field/init.ts @@ -59,13 +59,13 @@ export function initFields( if (store[name] === undefined) { // To prevent linking to the same type of component for non-primitive values, // it's important to clone the default value from the component constructor. - val = field.default !== undefined ? - field.default : + if (field.default !== undefined) { + val = field.default; - (() => { - const v = instance[name]; - return Object.isPrimitive(v) ? v : Object.fastClone(v); - })(); + } else { + const defValue = instance[name]; + val = Object.isPrimitive(defValue) ? defValue : Object.fastClone(defValue); + } store[name] = val; } From 34f8f39545319b7ae2c3b840142ba384392dc61f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 16 Sep 2024 15:47:25 +0300 Subject: [PATCH 135/334] fix: fixed `once` modifier for hook handlers --- src/core/component/hook/index.ts | 36 ++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/core/component/hook/index.ts b/src/core/component/hook/index.ts index 8017aba53b..4cc02220f1 100644 --- a/src/core/component/hook/index.ts +++ b/src/core/component/hook/index.ts @@ -75,16 +75,17 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn } default: { + let toDelete: CanNull = null; + if (hooks.some((hook) => hook.after != null && hook.after.size > 0)) { - const - emitter = new QueueEmitter(), - filteredHooks: ComponentHook[] = []; + const emitter = new QueueEmitter(); - hooks.forEach((hook) => { + hooks.forEach((hook, i) => { const nm = hook.name; - if (!hook.once) { - filteredHooks.push(hook); + if (hook.once) { + toDelete ??= []; + toDelete.push(i); } emitter.on(hook.after, () => { @@ -102,7 +103,7 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn }); }); - m.hooks[hook] = filteredHooks; + removeFromHooks(toDelete); const tasks = emitter.drain(); @@ -111,9 +112,9 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn } } else { - const tasks: Array> = []; + let tasks: CanNull>> = null; - hooks.slice().forEach((hook) => { + hooks.forEach((hook, i) => { let res: unknown; switch (args.length) { @@ -130,15 +131,20 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn } if (hook.once) { - hooks.pop(); + toDelete ??= []; + toDelete.push(i); } if (Object.isPromise(res)) { + tasks ??= []; tasks.push(res); } }); - if (tasks.length > 0) { + removeFromHooks(toDelete); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (tasks != null) { return Promise.all(tasks).then(() => undefined); } } @@ -146,4 +152,12 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn } return SyncPromise.resolve(); + + function removeFromHooks(toDelete: CanNull) { + if (toDelete != null) { + toDelete.reverse().forEach((i) => { + hooks.splice(i, 1); + }); + } + } } From 453381b24f73b16c0e228a829cb767134cb668f1 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 16 Sep 2024 15:53:27 +0300 Subject: [PATCH 136/334] refactor: use Array.toArray --- src/core/component/decorators/factory.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts index affa20380e..f688ea50e3 100644 --- a/src/core/component/decorators/factory.ts +++ b/src/core/component/decorators/factory.ts @@ -102,15 +102,12 @@ export function paramsFactory( function decorateMethod() { const name = key; - let { - watchers, - hooks - } = info; + let {watchers, hooks} = info; if (p.watch != null) { watchers ??= {}; - (Object.isArray(p.watch) ? p.watch : [p.watch]).forEach((watcher) => { + Array.toArray(p.watch).forEach((watcher) => { if (Object.isPlainObject(watcher)) { const path = String(watcher.path ?? watcher.field); watchers[path] = wrapOpts({...p.watchParams, ...watcher, path}); @@ -124,7 +121,7 @@ export function paramsFactory( if (p.hook != null) { hooks ??= {}; - (Object.isArray(p.hook) ? p.hook : [p.hook]).forEach((hook) => { + Array.toArray(p.hook).forEach((hook) => { if (Object.isSimpleObject(hook)) { const hookName = Object.keys(hook)[0], @@ -197,17 +194,14 @@ export function paramsFactory( const info = metaCluster[key] ?? {src: meta.componentName}; - let { - watchers, - after - } = info; + let {watchers, after} = info; if (p.after != null) { after = new Set([].concat(p.after)); } if (p.watch != null) { - (Object.isArray(p.watch) ? p.watch : [p.watch]).forEach((watcher) => { + Array.toArray(p.watch).forEach((watcher) => { watchers ??= new Map(); if (Object.isPlainObject(watcher)) { From b01a4b2f095adcd7f5743a8a06b7671acb51ac14 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 16 Sep 2024 15:56:39 +0300 Subject: [PATCH 137/334] refactor: test -> shouldInit --- src/components/super/i-block/event/index.ts | 2 +- src/components/super/i-data/data.ts | 2 +- src/components/super/i-data/handlers.ts | 4 ++-- src/core/component/decorators/interface/watcher.ts | 2 +- src/core/component/decorators/watch/README.md | 4 ++-- src/core/component/interface/watch.ts | 4 ++-- src/core/component/watch/bind.ts | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index ae897a3221..1b57990cd7 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -575,7 +575,7 @@ export default abstract class iBlockEvent extends iBlockBase { @watch({ path: 'proxyCall', immediate: true, - test: (ctx) => ctx.proxyCall != null + shouldInit: (ctx) => ctx.proxyCall != null }) protected initCallChildListener(enable: boolean): void { diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index 73588af38c..3a27bef06a 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -273,7 +273,7 @@ abstract class iDataData extends iBlock implements iDataProvider { */ @watch({ path: 'componentConverter', - test: (ctx) => ctx.componentConverter != null + shouldInit: (ctx) => ctx.componentConverter != null }) protected initRemoteData(): CanUndef { diff --git a/src/components/super/i-data/handlers.ts b/src/components/super/i-data/handlers.ts index 10dfb43ae3..c0a9a0d0bc 100644 --- a/src/components/super/i-data/handlers.ts +++ b/src/components/super/i-data/handlers.ts @@ -143,13 +143,13 @@ export default abstract class iDataHandlers extends iDataData { { path: 'dataProviderProp', provideArgs: false, - test: (ctx) => ctx.dataProviderProp != null + shouldInit: (ctx) => ctx.dataProviderProp != null }, { path: 'dataProviderOptions', provideArgs: false, - test: (ctx) => ctx.dataProviderOptions != null + shouldInit: (ctx) => ctx.dataProviderOptions != null } ]) diff --git a/src/core/component/decorators/interface/watcher.ts b/src/core/component/decorators/interface/watcher.ts index c7a631b137..3431f708cf 100644 --- a/src/core/component/decorators/interface/watcher.ts +++ b/src/core/component/decorators/interface/watcher.ts @@ -63,7 +63,7 @@ export interface DecoratorFieldWatcherObject< * * @param ctx */ - test?(ctx: CTX): boolean; + shouldInit?(ctx: CTX): boolean; } export interface DecoratorWatchHandler { diff --git a/src/core/component/decorators/watch/README.md b/src/core/component/decorators/watch/README.md index 29ed426f8f..9018993d87 100644 --- a/src/core/component/decorators/watch/README.md +++ b/src/core/component/decorators/watch/README.md @@ -189,7 +189,7 @@ Determines the execution timing of the event handler: 3. `sync` - the handler will be invoked immediately after each mutation. -### [test] +### [shouldInit] A function to determine whether a watcher should be initialized or not. If the function returns false, the watcher will not be initialized. @@ -203,7 +203,7 @@ export default class bExample extends iBlock { @prop({required: false}) params?: Dictionary; - @watch({path: 'params', test: (ctx) => ctx.params != null}) + @watch({path: 'params', shouldInit: (ctx) => ctx.params != null}) watcher() { } diff --git a/src/core/component/interface/watch.ts b/src/core/component/interface/watch.ts index 4da402bd7e..96e7ad9549 100644 --- a/src/core/component/interface/watch.ts +++ b/src/core/component/interface/watch.ts @@ -152,7 +152,7 @@ export interface WatchObject< * * @param ctx */ - test?(ctx: CTX): boolean; + shouldInit?(ctx: CTX): boolean; } export interface MethodWatcher< @@ -190,7 +190,7 @@ export interface MethodWatcher< * * @param ctx */ - test?(ctx: CTX): boolean; + shouldInit?(ctx: CTX): boolean; /** * An object with additional settings for the event emitter diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 8d1a85d7e2..0e89d808cb 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -114,7 +114,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // Iterates over all registered handlers for this watcher watchers!.forEach((watchInfo) => { - if (watchInfo.test?.(component) === false) { + if (watchInfo.shouldInit?.(component) === false) { return; } From afceb80494cfed799bd86331b43783648942ff59 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 17 Sep 2024 16:57:46 +0300 Subject: [PATCH 138/334] fix: retrieving information about the observed field should be done in beforeDataCreate --- src/components/friends/sync/mod.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/friends/sync/mod.ts b/src/components/friends/sync/mod.ts index e8b41d4e72..7b6a17bee3 100644 --- a/src/components/friends/sync/mod.ts +++ b/src/components/friends/sync/mod.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { getPropertyInfo } from 'core/component'; +import { getPropertyInfo, PropertyInfo } from 'core/component'; import { statuses } from 'components/super/i-block/const'; import type Sync from 'components/friends/sync/class'; @@ -101,7 +101,9 @@ export function mod( const that = this, - info = getPropertyInfo(path, this.component); + originalPath = path; + + let info: CanNull = null; if (this.lfc.isBeforeCreate()) { this.syncModCache[modName] = sync; @@ -122,6 +124,8 @@ export function mod( } function sync() { + info ??= getPropertyInfo(originalPath, that.component); + const {path} = info; let val: unknown; From 8bc70cededac63ff7d8a96d9443295bd52f8ea9b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 17 Sep 2024 17:00:01 +0300 Subject: [PATCH 139/334] fix: don't create abstract smart components --- src/core/component/decorators/component/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index ce24bcd58a..291e239ad9 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -128,7 +128,7 @@ export function component(opts?: ComponentOptions): Function { // If we have a smart component, // we need to compile two components at runtime - if (Object.isPlainObject(componentParams.functional)) { + if (!componentInfo.isAbstract && Object.isPlainObject(componentParams.functional)) { component({ ...opts, name: `${componentFullName}-functional`, From 49eed5a3ab50a89a84404b854047c09584f27ccd Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 20 Sep 2024 16:41:39 +0300 Subject: [PATCH 140/334] refactor: moved to composition API --- src/core/component/engines/vue3/component.ts | 168 +++++++++++-------- 1 file changed, 101 insertions(+), 67 deletions(-) diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index 5c25a1dc72..e4a732dc00 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -6,7 +6,6 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import SyncPromise from 'core/promise/sync'; import watch, { WatchHandler, WatchHandlerParams } from 'core/object/watch'; import * as init from 'core/component/init'; @@ -17,10 +16,29 @@ import { getComponentContext, dropRawComponentContext } from 'core/component/con import { wrapAPI } from 'core/component/render'; import type { ComponentEngine, ComponentOptions, SetupContext } from 'core/component/engines'; -import type { ComponentMeta } from 'core/component/interface'; +import type { ComponentInterface, ComponentMeta } from 'core/component/interface'; import { supports, proxyGetters } from 'core/component/engines/vue3/const'; +import { + + getCurrentInstance, + + onBeforeMount, + onMounted, + + onBeforeUpdate, + onUpdated, + + onBeforeUnmount, + onUnmounted, + + onErrorCaptured, + onServerPrefetch, + onRenderTriggered + +} from 'vue'; + import * as r from 'core/component/engines/vue3/render'; /** @@ -33,7 +51,13 @@ export function getComponent(meta: ComponentMeta): ComponentOptions = null, + unsafe: Nullable = null; + + ({ctx, unsafe} = getComponentContext(internalInstance!['proxy']!, true)); + + const {hooks} = meta; // @ts-ignore (unsafe) ctx['$renderEngine'] = {supports, proxyGetters, r, wrapAPI}; init.beforeCreateState(ctx, meta, {implementEventAPI: true}); - if (SSR) { - if (ctx.canFunctional !== true) { - this._.type.serverPrefetch = () => { - const init = unsafe.$initializer; + if (SSR && ctx.canFunctional !== true) { + onServerPrefetch(() => { + if (unsafe == null) { + return; + } + + return unsafe.$initializer; + }); + } - try { - // If init is a synchronous promise, we explicitly perform an `unwrap` to eliminate the extra microtask - return SyncPromise.resolve(init).unwrap(); + onBeforeMount(() => { + if (ctx == null) { + return; + } - } catch { - return init; - } - }; + init.createdState(ctx); + init.beforeMountState(ctx); + }); - } else { - delete this._.type.serverPrefetch; + onMounted(() => { + if (ctx == null) { + return; } - } - }, - created(): void { - init.createdState(getComponentContext(this)); - }, + init.mountedState(ctx); + }); - beforeMount(): void { - init.beforeMountState(getComponentContext(this)); - }, + onBeforeUpdate(() => { + if (ctx == null) { + return; + } - mounted(): void { - init.mountedState(getComponentContext(this)); - }, + init.beforeUpdateState(ctx); + }); - beforeUpdate(): void { - init.beforeUpdateState(getComponentContext(this)); - }, + onUpdated(() => { + if (ctx == null) { + return; + } - updated(): void { - init.updatedState(getComponentContext(this)); - }, + init.beforeUpdateState(ctx); + }); - activated(): void { - init.activatedState(getComponentContext(this)); - }, + onBeforeUnmount(() => { + if (ctx == null) { + return; + } - deactivated(): void { - init.deactivatedState(getComponentContext(this)); - }, + init.beforeDestroyState(ctx, {recursive: true}); + }); - beforeUnmount(): void { - const ctx = getComponentContext(this); + onUnmounted(() => { + if (ctx == null) { + return; + } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (ctx == null) { - return; - } + init.destroyedState(ctx); + dropRawComponentContext(ctx); - init.beforeDestroyState(ctx, {recursive: false}); - }, + ctx = null; + unsafe = null; + }); - unmounted(): void { - const ctx = getComponentContext(this); + if (hooks.errorCaptured.length > 0) { + onErrorCaptured((...args) => { + if (ctx == null) { + return; + } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (ctx == null) { - return; + init.errorCapturedState(ctx, ...args); + }); } - init.destroyedState(ctx); - dropRawComponentContext(ctx); - }, + if (hooks.renderTriggered.length > 0) { + onRenderTriggered((...args) => { + if (ctx == null) { + return; + } - errorCaptured(...args: unknown[]): void { - init.errorCapturedState(getComponentContext(this), ...args); - }, + init.renderTriggeredState(ctx, ...args); + }); + } - renderTriggered(...args: unknown[]): void { - init.renderTriggeredState(getComponentContext(this), ...args); + return meta.methods.setup?.fn(props, setupCtx); } }; } From e797d7755d62134824a645759faa52921d08b24c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 15:16:40 +0300 Subject: [PATCH 141/334] feat: added a method to determine which specific props have been added to the component --- src/components/super/i-block/props.ts | 3 +++ src/core/component/interface/component/component.ts | 5 +++++ src/core/component/render/wrappers.ts | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/src/components/super/i-block/props.ts b/src/components/super/i-block/props.ts index 5d6b0658cf..5671eff74f 100644 --- a/src/components/super/i-block/props.ts +++ b/src/components/super/i-block/props.ts @@ -317,4 +317,7 @@ export default abstract class iBlockProps extends ComponentInterface { @prop({type: Function, required: false}) override readonly getParent?: () => this['$parent']; + + @prop({type: Function, required: false}) + override readonly getPassedProps?: () => Set; } diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index ad5d8e7114..8e00ecb288 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -162,6 +162,11 @@ export abstract class ComponentInterface { */ abstract readonly getParent?: () => this['$parent']; + /** + * The getter is used to get a set of props that were passed to the component directly through the template + */ + abstract readonly getPassedProps?: () => Set; + /** * A string value indicating the lifecycle hook that the component is currently in. * For instance, `created`, `mounted` or `destroyed`. diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 7065e4ae21..ac6557ddc4 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -123,6 +123,11 @@ export function wrapCreateBlock(original: T): T { vnode = createVNode(name, attrs, isRegular ? slots : [], patchFlag, dynamicProps); vnode.props ??= {}; + + let props: CanNull> = null; + + vnode.props.getPassedProps ??= () => props ??= new Set(Object.keys(vnode.props!)); + vnode.props.getRoot ??= this.$getRoot(this); vnode.props.getParent ??= () => vnode.virtualParent?.value != null ? From 90e7b0537dd1648ba3f7a97532b576e27e696455 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 15:17:51 +0300 Subject: [PATCH 142/334] chore: added an optimization that prevents watching a prop if it is not passed through the template --- src/components/friends/sync/link.ts | 80 ++++++++++++--------- src/components/super/i-block/base/index.ts | 24 +++++++ src/core/component/watch/bind.ts | 84 ++++++++++++++-------- src/core/component/watch/create.ts | 11 ++- 4 files changed, 133 insertions(+), 66 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 2d3d5f30e3..9c868a8e1d 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -247,6 +247,7 @@ export function link( getter?: LinkGetter ): CanUndef; +// eslint-disable-next-line complexity export function link( this: Sync, path?: LinkDecl | AsyncWatchOptions | LinkGetter, @@ -390,51 +391,64 @@ export function link( return resolveVal; }; - if (getter != null && (getter.length > 1 || getter['originalLength'] > 1)) { - ctx.watch(srcInfo ?? normalizedPath, resolvedOpts, (val: unknown, oldVal: unknown, ...args: unknown[]) => { - if (customWatcher) { - oldVal = undefined; + let canSkipWatching = !resolvedOpts.immediate; - } else { - if (args.length === 0 && Object.isArray(val) && val.length > 0) { - const mutation = <[unknown, unknown]>val[val.length - 1]; + // We cannot observe props and attributes on a component if it is a root component, a functional component, + // or if it does not accept such parameters in the template + if (!canSkipWatching && srcInfo.type === 'prop' || srcInfo.type === 'attr') { + canSkipWatching = + meta.params.root === true || + ctx.isFunctional || + srcInfo.ctx.getPassedProps?.().has(srcInfo.name) === false; + } - val = mutation[0]; - oldVal = mutation[1]; - } + if (!canSkipWatching) { + if (getter != null && (getter.length > 1 || getter['originalLength'] > 1)) { + ctx.watch(srcInfo ?? normalizedPath, resolvedOpts, (val: unknown, oldVal: unknown, ...args: unknown[]) => { + if (customWatcher) { + oldVal = undefined; + + } else { + if (args.length === 0 && Object.isArray(val) && val.length > 0) { + const mutation = <[unknown, unknown]>val[val.length - 1]; + + val = mutation[0]; + oldVal = mutation[1]; + } - if (Object.isTruly(compareNewAndOldValue.call(this, val, oldVal, destPath, resolvedOpts))) { - return; + if (Object.isTruly(compareNewAndOldValue.call(this, val, oldVal, destPath, resolvedOpts))) { + return; + } } - } - sync(val, oldVal); - }); + sync(val, oldVal); + }); - } else { - ctx.watch(srcInfo ?? normalizedPath, resolvedOpts, (val: unknown, ...args: unknown[]) => { - let - oldVal: unknown = undefined; + } else { + ctx.watch(srcInfo ?? normalizedPath, resolvedOpts, (val: unknown, ...args: unknown[]) => { + let + oldVal: unknown = undefined; - if (!customWatcher) { - if (args.length === 0 && Object.isArray(val) && val.length > 0) { - const - mutation = <[unknown, unknown]>val[val.length - 1]; + if (!customWatcher) { + if (args.length === 0 && Object.isArray(val) && val.length > 0) { + const + mutation = <[unknown, unknown]>val[val.length - 1]; - val = mutation[0]; - oldVal = mutation[1]; + val = mutation[0]; + oldVal = mutation[1]; - } else { - oldVal ??= args[0]; - } + } else { + oldVal ??= args[0]; + } - if (Object.isTruly(compareNewAndOldValue.call(this, val, oldVal, destPath, resolvedOpts))) { - return; + if (Object.isTruly(compareNewAndOldValue.call(this, val, oldVal, destPath, resolvedOpts))) { + return; + } } - } - sync(val, oldVal); - }); + sync(val, oldVal); + }); + } } { diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 6ee4afa480..34d94a7c0a 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -14,6 +14,7 @@ import symbolGenerator from 'core/symbol'; import log, { LogMessageOptions } from 'core/log'; +import { isProxy } from 'core/object/watch'; import type Async from 'core/async'; @@ -33,6 +34,7 @@ import { component, getComponentName, + getPropertyInfo, bindRemoteWatchers, isCustomWatcher, @@ -494,6 +496,28 @@ export default abstract class iBlockBase extends iBlockFriends { } void this.lfc.execCbAfterComponentCreated(() => { + let info = Object.isString(path) ? getPropertyInfo(path, this) : null; + + // TODO: Implement a more accurate check + if (info == null && !isProxy(path)) { + info = Object.cast(path); + } + + let canSkipWatching = !opts.immediate; + + // We cannot observe props and attributes on a component if it is a root component, a functional component, + // or if it does not accept such parameters in the template + if (!canSkipWatching && info != null && (info.type === 'prop' || info.type === 'attr')) { + canSkipWatching = + this.meta.params.root === true || + this.isFunctional || + info.ctx.getPassedProps?.().has(info.name) === false; + } + + if (!canSkipWatching) { + return () => undefined; + } + let // eslint-disable-next-line prefer-const link: Nullable>, diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 0e89d808cb..22d5718ec4 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -283,30 +283,42 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - /* eslint-disable prefer-const */ - let link: Nullable, unwatch: Nullable; - /* eslint-enable prefer-const */ + const toWatch = p.info ?? getPropertyInfo(watchPath, component); - const emitter: EventEmitterLikeP = (_, wrappedHandler) => { - handler = Object.cast(wrappedHandler); + let canSkipWatching = !watchInfo.immediate; - $a.worker(() => { - if (link != null) { - $a.off(link); - } - }, asyncParams); + // We cannot observe props and attributes on a component if it is a root component, a functional component, + // or if it does not accept such parameters in the template + if (!canSkipWatching && toWatch.type === 'prop' || toWatch.type === 'attr') { + canSkipWatching = + meta.params.root === true || + meta.params.functional !== true || + toWatch.ctx.getPassedProps?.().has(toWatch.name) === false; + } - return () => unwatch?.(); - }; + if (canSkipWatching) { + unwatch = () => undefined; - link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); + } else { + const emitter: EventEmitterLikeP = (_, wrappedHandler) => { + handler = Object.cast(wrappedHandler); - const toWatch = p.info ?? getPropertyInfo(watchPath, component); - unwatch = $watch.call(component, toWatch, watchInfo, handler); + $a.worker(() => { + if (link != null) { + $a.off(link); + } + }, asyncParams); + + return () => unwatch?.(); + }; + + link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); + unwatch = $watch.call(component, toWatch, watchInfo, handler); + } }).catch(stderr); } else { @@ -338,30 +350,42 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - /* eslint-disable prefer-const */ - let link: Nullable, unwatch: Nullable; - /* eslint-enable prefer-const */ + const toWatch = p.info ?? getPropertyInfo(watchPath, component); - const emitter: EventEmitterLikeP = (_, wrappedHandler) => { - handler = Object.cast(wrappedHandler); + let canSkipWatching = !watchInfo.immediate; - $a.worker(() => { - if (link != null) { - $a.off(link); - } - }, asyncParams); + // We cannot observe props and attributes on a component if it is a root component, a functional component, + // or if it does not accept such parameters in the template + if (!canSkipWatching && toWatch.type === 'prop' || toWatch.type === 'attr') { + canSkipWatching = + meta.params.root === true || + meta.params.functional !== true || + toWatch.ctx.getPassedProps?.().has(toWatch.name) === false; + } - return () => unwatch?.(); - }; + if (canSkipWatching) { + unwatch = () => undefined; - link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); + } else { + const emitter: EventEmitterLikeP = (_, wrappedHandler) => { + handler = Object.cast(wrappedHandler); - const toWatch = p.info ?? getPropertyInfo(watchPath, component); - unwatch = $watch.call(component, toWatch, watchInfo, handler); + $a.worker(() => { + if (link != null) { + $a.off(link); + } + }, asyncParams); + + return () => unwatch?.(); + }; + + link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); + unwatch = $watch.call(component, toWatch, watchInfo, handler); + } } }); } diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 32fa890c2f..cc464b8850 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -57,6 +57,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface info = getPropertyInfo(path, component); } else { + // TODO: Implement a more accurate check if (isProxy(path)) { info = Object.cast({ctx: path}); @@ -88,9 +89,13 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface const isDefinedPath = Object.size(info.path) > 0; - let canSkipWatching = - (isRoot || isFunctional) && - (info.type === 'prop' || info.type === 'attr'); + let canSkipWatching = false; + + // We cannot observe props and attributes on a component if it is a root component, a functional component, + // or if it does not accept such parameters in the template + if (info.type === 'prop' || info.type === 'attr') { + canSkipWatching = isRoot || isFunctional || info.ctx.getPassedProps?.().has(info.name) === false; + } if (!canSkipWatching && isFunctional) { let field: Nullable; From d6e9a1806488cd44f6e386a719957a1d8aae7195 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 15:18:04 +0300 Subject: [PATCH 143/334] chore: stylish fixes --- src/components/super/i-block/modules/listeners/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/super/i-block/modules/listeners/index.ts b/src/components/super/i-block/modules/listeners/index.ts index 9b325b7103..fb65945e29 100644 --- a/src/components/super/i-block/modules/listeners/index.ts +++ b/src/components/super/i-block/modules/listeners/index.ts @@ -19,8 +19,7 @@ import iBlock from 'components/super/i-block/i-block'; const $$ = symbolGenerator(); -let - baseInitLoad; +let baseInitLoad; /** * Initializes the listening of global application events for the component From bcd8206293bbe758fa042de022ae823dedd2d348 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 16:22:33 +0300 Subject: [PATCH 144/334] fix: fixed bugs --- src/components/friends/sync/link.ts | 13 ++++++----- src/components/super/i-block/base/index.ts | 14 +++++++----- src/core/component/watch/bind.ts | 26 +++++++++++++--------- src/core/component/watch/create.ts | 8 +++++-- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 9c868a8e1d..fa255059fe 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -394,12 +394,15 @@ export function link( let canSkipWatching = !resolvedOpts.immediate; // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template - if (!canSkipWatching && srcInfo.type === 'prop' || srcInfo.type === 'attr') { + // or if it does not accept such parameters in the template. + // Also, prop watching does not work during SSR. + if (canSkipWatching && (srcInfo.type === 'prop' || srcInfo.type === 'attr')) { + const {ctx, ctx: {unsafe: {meta: {params}}}} = srcInfo; + canSkipWatching = - meta.params.root === true || - ctx.isFunctional || - srcInfo.ctx.getPassedProps?.().has(srcInfo.name) === false; + SSR || + params.root === true || params.functional === true || + ctx.getPassedProps?.().has(srcInfo.name) === false; } if (!canSkipWatching) { diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 34d94a7c0a..7c64779c87 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -506,12 +506,16 @@ export default abstract class iBlockBase extends iBlockFriends { let canSkipWatching = !opts.immediate; // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template - if (!canSkipWatching && info != null && (info.type === 'prop' || info.type === 'attr')) { + // or if it does not accept such parameters in the template. + // Also, prop watching does not work during SSR. + if (canSkipWatching && info != null && (info.type === 'prop' || info.type === 'attr')) { + const {ctx, ctx: {unsafe: {meta: {params}}}} = info; + canSkipWatching = - this.meta.params.root === true || - this.isFunctional || - info.ctx.getPassedProps?.().has(info.name) === false; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + SSR || + params.root === true || params.functional === true || + ctx.getPassedProps?.().has(info.name) === false; } if (!canSkipWatching) { diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 22d5718ec4..e21c7acaca 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -292,12 +292,15 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR let canSkipWatching = !watchInfo.immediate; // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template - if (!canSkipWatching && toWatch.type === 'prop' || toWatch.type === 'attr') { + // or if it does not accept such parameters in the template. + // Also, prop watching does not work during SSR. + if (canSkipWatching && (toWatch.type === 'prop' || toWatch.type === 'attr')) { + const {ctx, ctx: {unsafe: {meta: {params}}}} = toWatch; + canSkipWatching = - meta.params.root === true || - meta.params.functional !== true || - toWatch.ctx.getPassedProps?.().has(toWatch.name) === false; + SSR || + params.root === true || params.functional === true || + ctx.getPassedProps?.().has(toWatch.name) === false; } if (canSkipWatching) { @@ -359,12 +362,15 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR let canSkipWatching = !watchInfo.immediate; // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template - if (!canSkipWatching && toWatch.type === 'prop' || toWatch.type === 'attr') { + // or if it does not accept such parameters in the template. + // Also, prop watching does not work during SSR. + if (canSkipWatching && (toWatch.type === 'prop' || toWatch.type === 'attr')) { + const {ctx, ctx: {unsafe: {meta: {params}}}} = toWatch; + canSkipWatching = - meta.params.root === true || - meta.params.functional !== true || - toWatch.ctx.getPassedProps?.().has(toWatch.name) === false; + SSR || + params.root === true || params.functional === true || + ctx.getPassedProps?.().has(toWatch.name) === false; } if (canSkipWatching) { diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index cc464b8850..f955803bc8 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -92,9 +92,13 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface let canSkipWatching = false; // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template + // or if it does not accept such parameters in the template. + // Also, prop watching does not work during SSR. if (info.type === 'prop' || info.type === 'attr') { - canSkipWatching = isRoot || isFunctional || info.ctx.getPassedProps?.().has(info.name) === false; + canSkipWatching = + SSR || + isRoot || isFunctional || + info.ctx.getPassedProps?.().has(info.name) === false; } if (!canSkipWatching && isFunctional) { From 5f60ec91a291912b0683fc541d65d04ff32c5719 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 16:40:39 +0300 Subject: [PATCH 145/334] fix: fixed a typo --- src/components/super/i-block/base/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 7c64779c87..8ffd5e3ae1 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -518,7 +518,7 @@ export default abstract class iBlockBase extends iBlockFriends { ctx.getPassedProps?.().has(info.name) === false; } - if (!canSkipWatching) { + if (canSkipWatching) { return () => undefined; } From 6854d1462c12774c2c3a566fca4d4b84fe0d5d3e Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 16:55:30 +0300 Subject: [PATCH 146/334] fix: fixed bugs --- src/components/friends/sync/link.ts | 3 +++ src/components/super/i-block/base/index.ts | 3 +++ src/core/component/watch/bind.ts | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index fa255059fe..9ef66a9c77 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -403,6 +403,9 @@ export function link( SSR || params.root === true || params.functional === true || ctx.getPassedProps?.().has(srcInfo.name) === false; + + } else { + canSkipWatching = false; } if (!canSkipWatching) { diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 8ffd5e3ae1..df5d0a0d91 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -516,6 +516,9 @@ export default abstract class iBlockBase extends iBlockFriends { SSR || params.root === true || params.functional === true || ctx.getPassedProps?.().has(info.name) === false; + + } else { + canSkipWatching = false; } if (canSkipWatching) { diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index e21c7acaca..2905217e0e 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -301,6 +301,9 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR SSR || params.root === true || params.functional === true || ctx.getPassedProps?.().has(toWatch.name) === false; + + } else { + canSkipWatching = false; } if (canSkipWatching) { @@ -371,6 +374,9 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR SSR || params.root === true || params.functional === true || ctx.getPassedProps?.().has(toWatch.name) === false; + + } else { + canSkipWatching = false; } if (canSkipWatching) { From 8f23dd64a07ba922673ee2e7cf897942b57988bd Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 17:28:55 +0300 Subject: [PATCH 147/334] fix: fixed `getPassedProps` for functional components --- src/core/component/render/wrappers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index ac6557ddc4..35466e8f03 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -126,14 +126,14 @@ export function wrapCreateBlock(original: T): T { let props: CanNull> = null; - vnode.props.getPassedProps ??= () => props ??= new Set(Object.keys(vnode.props!)); - vnode.props.getRoot ??= this.$getRoot(this); vnode.props.getParent ??= () => vnode.virtualParent?.value != null ? vnode.virtualParent.value : this; + vnode.props.getPassedProps ??= () => props ??= new Set(attrs != null ? Object.keys(attrs) : []); + // For refs within functional components, // it is necessary to explicitly set a reference to the instance of the component if (!SSR && vnode.ref != null && vnode.ref.i == null) { From b2c0807d61b35018df6b06e7c5e1b4b0c95e090d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 17:30:26 +0300 Subject: [PATCH 148/334] chore: refactoring --- src/core/component/render/wrappers.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 35466e8f03..0f396fd9f7 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -124,15 +124,14 @@ export function wrapCreateBlock(original: T): T { vnode.props ??= {}; - let props: CanNull> = null; - vnode.props.getRoot ??= this.$getRoot(this); vnode.props.getParent ??= () => vnode.virtualParent?.value != null ? vnode.virtualParent.value : this; - vnode.props.getPassedProps ??= () => props ??= new Set(attrs != null ? Object.keys(attrs) : []); + let passedProps: CanNull> = null; + vnode.props.getPassedProps ??= () => passedProps ??= new Set(attrs != null ? Object.keys(attrs) : []); // For refs within functional components, // it is necessary to explicitly set a reference to the instance of the component From eebbf5e0f92a3cc9ebc85c29bb2412f9de69de41 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 17:59:41 +0300 Subject: [PATCH 149/334] fix: fixed bugs --- src/components/friends/sync/link.ts | 15 +++++++---- src/components/super/i-block/base/index.ts | 17 +++++++----- src/core/component/watch/bind.ts | 30 ++++++++++++++-------- src/core/component/watch/create.ts | 13 +++++++--- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 9ef66a9c77..e23b240cb2 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -397,12 +397,17 @@ export function link( // or if it does not accept such parameters in the template. // Also, prop watching does not work during SSR. if (canSkipWatching && (srcInfo.type === 'prop' || srcInfo.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta: {params}}}} = srcInfo; + const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = srcInfo; - canSkipWatching = - SSR || - params.root === true || params.functional === true || - ctx.getPassedProps?.().has(srcInfo.name) === false; + canSkipWatching = SSR || params.root === true || params.functional === true; + + if (!canSkipWatching) { + const + prop = meta.props[srcInfo.name], + propName = prop?.forceUpdate !== false ? srcInfo.name : `on:${srcInfo.name}`; + + canSkipWatching = ctx.getPassedProps?.().has(propName) === false; + } } else { canSkipWatching = false; diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index df5d0a0d91..9f0212f1b6 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -509,13 +509,18 @@ export default abstract class iBlockBase extends iBlockFriends { // or if it does not accept such parameters in the template. // Also, prop watching does not work during SSR. if (canSkipWatching && info != null && (info.type === 'prop' || info.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta: {params}}}} = info; + const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = info; - canSkipWatching = - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - SSR || - params.root === true || params.functional === true || - ctx.getPassedProps?.().has(info.name) === false; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + canSkipWatching = SSR || params.root === true || params.functional === true; + + if (!canSkipWatching) { + const + prop = meta.props[info.name], + propName = prop?.forceUpdate !== false ? info.name : `on:${info.name}`; + + canSkipWatching = ctx.getPassedProps?.().has(propName) === false; + } } else { canSkipWatching = false; diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 2905217e0e..fcdb74c7ab 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -295,12 +295,17 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // or if it does not accept such parameters in the template. // Also, prop watching does not work during SSR. if (canSkipWatching && (toWatch.type === 'prop' || toWatch.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta: {params}}}} = toWatch; + const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = toWatch; - canSkipWatching = - SSR || - params.root === true || params.functional === true || - ctx.getPassedProps?.().has(toWatch.name) === false; + canSkipWatching = SSR || params.root === true || params.functional === true; + + if (!canSkipWatching) { + const + prop = meta.props[toWatch.name], + propName = prop?.forceUpdate !== false ? toWatch.name : `on:${toWatch.name}`; + + canSkipWatching = ctx.getPassedProps?.().has(propName) === false; + } } else { canSkipWatching = false; @@ -368,12 +373,17 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // or if it does not accept such parameters in the template. // Also, prop watching does not work during SSR. if (canSkipWatching && (toWatch.type === 'prop' || toWatch.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta: {params}}}} = toWatch; + const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = toWatch; - canSkipWatching = - SSR || - params.root === true || params.functional === true || - ctx.getPassedProps?.().has(toWatch.name) === false; + canSkipWatching = SSR || params.root === true || params.functional === true; + + if (!canSkipWatching) { + const + prop = meta.props[toWatch.name], + propName = prop?.forceUpdate !== false ? toWatch.name : `on:${toWatch.name}`; + + canSkipWatching = ctx.getPassedProps?.().has(propName) === false; + } } else { canSkipWatching = false; diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index f955803bc8..f757758057 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -95,10 +95,15 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface // or if it does not accept such parameters in the template. // Also, prop watching does not work during SSR. if (info.type === 'prop' || info.type === 'attr') { - canSkipWatching = - SSR || - isRoot || isFunctional || - info.ctx.getPassedProps?.().has(info.name) === false; + canSkipWatching = SSR || isRoot || isFunctional; + + if (!canSkipWatching) { + const + prop = info.ctx.meta.props[info.name], + propName = prop?.forceUpdate !== false ? info.name : `on:${info.name}`; + + canSkipWatching = info.ctx.getPassedProps?.().has(propName) === false; + } } if (!canSkipWatching && isFunctional) { From 6be75c6e1a270586ae2327e2f274ef9f8d1902ca Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 18:11:52 +0300 Subject: [PATCH 150/334] fix: fixed bugs --- src/components/friends/sync/link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index e23b240cb2..988bc6516a 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -396,7 +396,7 @@ export function link( // We cannot observe props and attributes on a component if it is a root component, a functional component, // or if it does not accept such parameters in the template. // Also, prop watching does not work during SSR. - if (canSkipWatching && (srcInfo.type === 'prop' || srcInfo.type === 'attr')) { + if (canSkipWatching && srcInfo != null && (srcInfo.type === 'prop' || srcInfo.type === 'attr')) { const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = srcInfo; canSkipWatching = SSR || params.root === true || params.functional === true; From a6b6f44fa43ff451c6b035d7b4d004df18a891b4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 18:30:03 +0300 Subject: [PATCH 151/334] fix: fixed using with a comment' node --- src/components/friends/block/block.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/friends/block/block.ts b/src/components/friends/block/block.ts index 733883f1d7..8d66531e5f 100644 --- a/src/components/friends/block/block.ts +++ b/src/components/friends/block/block.ts @@ -214,7 +214,7 @@ export function removeMod(this: Block, name: string, value?: unknown, reason: Mo return false; } - if (node != null) { + if (node instanceof Element) { node.classList.remove(this.getFullBlockName(name, currentValue)); } From e38da6e948d9b91f78a8d04a4f00babb8b264105 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 24 Sep 2024 18:45:29 +0300 Subject: [PATCH 152/334] fix: fixed beforeCreate/created hooks --- src/core/component/engines/vue3/component.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index e4a732dc00..e5dc2e2e28 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -95,6 +95,15 @@ export function getComponent(meta: ComponentMeta): ComponentOptions { if (unsafe == null) { @@ -126,7 +133,6 @@ export function getComponent(meta: ComponentMeta): ComponentOptions Date: Tue, 24 Sep 2024 18:53:42 +0300 Subject: [PATCH 153/334] fix: fixed bugs --- src/core/component/engines/vue3/component.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index e5dc2e2e28..97ececaea8 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -100,10 +100,6 @@ export function getComponent(meta: ComponentMeta): ComponentOptions { From 8bf7c33a85642cf6a8e8cfe39e7e0c472b06b699 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 25 Sep 2024 14:47:05 +0300 Subject: [PATCH 154/334] feat: added support for `renderTracked` & fixed bugs --- src/core/component/engines/vue3/component.ts | 40 ++++++++++++------- src/core/component/init/states/index.ts | 1 + .../component/init/states/render-tracked.ts | 21 ++++++++++ .../component/init/states/render-trigered.ts | 6 +-- src/core/component/interface/lc.ts | 5 ++- src/core/component/meta/create.ts | 5 ++- 6 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 src/core/component/init/states/render-tracked.ts diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index 97ececaea8..d8896c7d46 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -35,6 +35,8 @@ import { onErrorCaptured, onServerPrefetch, + + onRenderTracked, onRenderTriggered } from 'vue'; @@ -95,8 +97,12 @@ export function getComponent(meta: ComponentMeta): ComponentOptions { if (unsafe == null) { @@ -177,26 +180,33 @@ export function getComponent(meta: ComponentMeta): ComponentOptions 0) { - onErrorCaptured((...args) => { - if (ctx == null) { - return; - } + onErrorCaptured((...args) => { + if (ctx == null) { + return; + } - init.errorCapturedState(ctx, ...args); - }); - } + init.errorCapturedState(ctx, ...args); + }); - if (hooks.renderTriggered.length > 0) { - onRenderTriggered((...args) => { + // The capturing of this hook slows down the development build of the application, so we enable it optionally + if (hooks.renderTracked.length > 0) { + onRenderTracked((...args) => { if (ctx == null) { return; } - init.renderTriggeredState(ctx, ...args); + init.renderTrackedState(ctx, ...args); }); } + onRenderTriggered((...args) => { + if (ctx == null) { + return; + } + + init.renderTriggeredState(ctx, ...args); + }); + return meta.methods.setup?.fn(props, setupCtx); } }; diff --git a/src/core/component/init/states/index.ts b/src/core/component/init/states/index.ts index cdccf0af5e..bced812eb0 100644 --- a/src/core/component/init/states/index.ts +++ b/src/core/component/init/states/index.ts @@ -18,4 +18,5 @@ export * from 'core/component/init/states/deactivated'; export * from 'core/component/init/states/before-destroy'; export * from 'core/component/init/states/destroyed'; export * from 'core/component/init/states/error-captured'; +export * from 'core/component/init/states/render-tracked'; export * from 'core/component/init/states/render-trigered'; diff --git a/src/core/component/init/states/render-tracked.ts b/src/core/component/init/states/render-tracked.ts new file mode 100644 index 0000000000..dff999884c --- /dev/null +++ b/src/core/component/init/states/render-tracked.ts @@ -0,0 +1,21 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { runHook } from 'core/component/hook'; + +import type { ComponentInterface } from 'core/component/interface'; + +/** + * Initializes the "renderCaptured" state to the specified component instance + * + * @param component + * @param args - additional arguments + */ +export function renderTrackedState(component: ComponentInterface, ...args: unknown[]): void { + runHook('renderTracked', component, ...args).catch(stderr); +} diff --git a/src/core/component/init/states/render-trigered.ts b/src/core/component/init/states/render-trigered.ts index f4ca9020f3..cebb5be4a8 100644 --- a/src/core/component/init/states/render-trigered.ts +++ b/src/core/component/init/states/render-trigered.ts @@ -8,8 +8,6 @@ import { runHook } from 'core/component/hook'; -import { callMethodFromComponent } from 'core/component/method'; - import type { ComponentInterface } from 'core/component/interface'; /** @@ -19,7 +17,5 @@ import type { ComponentInterface } from 'core/component/interface'; * @param args - additional arguments */ export function renderTriggeredState(component: ComponentInterface, ...args: unknown[]): void { - runHook('renderTriggered', component, ...args).then(() => { - callMethodFromComponent(component, 'renderTriggered', ...args); - }).catch(stderr); + runHook('renderTriggered', component, ...args).catch(stderr); } diff --git a/src/core/component/interface/lc.ts b/src/core/component/interface/lc.ts index f61cdce65f..9efb7e8041 100644 --- a/src/core/component/interface/lc.ts +++ b/src/core/component/interface/lc.ts @@ -26,5 +26,6 @@ export type Hook = 'deactivated' | 'beforeDestroy' | 'destroyed' | - 'renderTriggered' | - 'errorCaptured'; + 'errorCaptured' | + 'renderTracked' | + 'renderTriggered'; diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index 204e6b50bc..b5bd7e2523 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -60,8 +60,9 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { deactivated: [], beforeDestroy: [], destroyed: [], - renderTriggered: [], - errorCaptured: [] + errorCaptured: [], + renderTracked: [], + renderTriggered: [] }, component: { From 75f88ee6d8f303002b6ef4a40877792babac2e34 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 25 Sep 2024 15:11:16 +0300 Subject: [PATCH 155/334] fix: fixed override --- src/components/super/i-block/base/index.ts | 4 ++- src/components/super/i-block/state/index.ts | 30 ++++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 6ee4afa480..b19ffcbeae 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -167,7 +167,9 @@ export default abstract class iBlockBase extends iBlockFriends { */ @computed({dependencies: []}) get rootAttrs(): Dictionary { - return this.field.getFieldsStore().rootAttrsStore; + return this.meta.fields.rootAttrsStore != null ? + this.field.getFieldsStore().rootAttrsStore : + this.rootAttrsStore; } /** diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index b92451da74..d3a8380129 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -163,8 +163,16 @@ export default abstract class iBlockState extends iBlockMods { */ @computed({cache: false, dependencies: []}) get componentStatus(): ComponentStatus { + if (this.shadowComponentStatusStore != null) { + return this.shadowComponentStatusStore; + } + + const status = this.meta.fields.componentStatusStore != null ? + this.field.getFieldsStore().componentStatusStore : + this.componentStatusStore; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - return this.shadowComponentStatusStore ?? this.field.getFieldsStore().componentStatusStore ?? 'unloaded'; + return status ?? 'unloaded'; } /** @@ -196,7 +204,13 @@ export default abstract class iBlockState extends iBlockMods { } else { this.shadowComponentStatusStore = undefined; - this.field.getFieldsStore().componentStatusStore = value; + + if (this.meta.fields.componentStatusStore != null) { + this.field.getFieldsStore().componentStatusStore = value; + + } else { + this.componentStatusStore = value; + } if (this.isReady && this.dependencies.length > 0) { void this.forceUpdate(); @@ -245,7 +259,9 @@ export default abstract class iBlockState extends iBlockMods { */ @computed({cache: false, dependencies: []}) get stage(): CanUndef { - return this.field.getFieldsStore().stageStore; + return this.meta.fields.stageStore != null ? + this.field.getFieldsStore().stageStore : + this.stageStore; } /** @@ -265,7 +281,13 @@ export default abstract class iBlockState extends iBlockMods { } this.async.clearAll({group: this.stageGroup}); - this.field.getFieldsStore().stageStore = value; + + if (this.meta.fields.stageStore != null) { + this.field.getFieldsStore().stageStore = value; + + } else { + this.stageStore = value; + } if (value != null) { this.emit(`stage:${value}`, value, oldValue); From 98be9007a1113e0b7f51a694121d686c1171f149 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 25 Sep 2024 17:06:12 +0300 Subject: [PATCH 156/334] chore: optimization --- src/components/super/i-block/base/index.ts | 24 ++++++++++++------- .../super/i-block/modules/lfc/index.ts | 20 ++++++++++++---- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 8430522c85..e2eb32d784 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -466,7 +466,9 @@ export default abstract class iBlockBase extends iBlockFriends { return; } - const {async: $a} = this; + const that = this; + + const {meta: {hooks}, async: $a} = this; let handler: RawWatchHandler, @@ -497,8 +499,15 @@ export default abstract class iBlockBase extends iBlockFriends { return; } - void this.lfc.execCbAfterComponentCreated(() => { - let info = Object.isString(path) ? getPropertyInfo(path, this) : null; + if (this.lfc.isBeforeCreate()) { + hooks['before:created'].push({fn: initWatcher}); + + } else { + initWatcher(); + } + + function initWatcher() { + let info = Object.isString(path) ? getPropertyInfo(path, that) : null; // TODO: Implement a more accurate check if (info == null && !isProxy(path)) { @@ -513,7 +522,6 @@ export default abstract class iBlockBase extends iBlockFriends { if (canSkipWatching && info != null && (info.type === 'prop' || info.type === 'attr')) { const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = info; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition canSkipWatching = SSR || params.root === true || params.functional === true; if (!canSkipWatching) { @@ -529,7 +537,7 @@ export default abstract class iBlockBase extends iBlockFriends { } if (canSkipWatching) { - return () => undefined; + return; } let @@ -539,7 +547,7 @@ export default abstract class iBlockBase extends iBlockFriends { // eslint-disable-next-line prefer-const unwatch: Nullable; - const emitter: Function = (_: any, wrappedHandler: RawWatchHandler) => { + const emitter: Function = (_: any, wrappedHandler: RawWatchHandler) => { wrappedHandler['originalLength'] = handler['originalLength'] ?? handler.length; handler = wrappedHandler; @@ -553,8 +561,8 @@ export default abstract class iBlockBase extends iBlockFriends { }; link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(opts, 'watchers')); - unwatch = this.$watch(Object.cast(path), opts, handler); - }); + unwatch = that.$watch(Object.cast(path), opts, handler); + } } /** diff --git a/src/components/super/i-block/modules/lfc/index.ts b/src/components/super/i-block/modules/lfc/index.ts index 09cf673ad7..ee125b136e 100644 --- a/src/components/super/i-block/modules/lfc/index.ts +++ b/src/components/super/i-block/modules/lfc/index.ts @@ -75,9 +75,15 @@ export default class Lfc extends Friend { */ execCbAtTheRightTime(cb: Cb, opts?: AsyncOptions): CanPromise> { if (this.isBeforeCreate('beforeDataCreate')) { - return this.async.promise(new SyncPromise((r) => { + const promise = new SyncPromise((r) => { this.meta.hooks.beforeDataCreate.push({fn: () => r(cb.call(this.component))}); - }), opts).catch(stderr); + }); + + if (opts != null) { + return this.async.promise(promise, opts).catch(stderr); + } + + return promise; } if (this.hook === 'beforeDataCreate') { @@ -143,9 +149,15 @@ export default class Lfc extends Friend { */ execCbAfterComponentCreated(cb: Cb, opts?: AsyncOptions): CanPromise> { if (this.isBeforeCreate()) { - return this.async.promise(new SyncPromise((r) => { + const promise = new SyncPromise((r) => { this.meta.hooks['before:created'].push({fn: () => r(cb.call(this.component))}); - }), opts).catch(stderr); + }); + + if (opts != null) { + return this.async.promise(promise, opts).catch(stderr); + } + + return promise; } if (statuses[this.componentStatus] >= 0) { From 9bb1e2dfb3dec19d6d0ed1a57fc141152611dab7 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 26 Sep 2024 14:53:42 +0300 Subject: [PATCH 157/334] refactor: extract to the helper --- src/components/friends/sync/link.ts | 26 +---- src/components/super/i-block/base/index.ts | 25 +---- src/core/component/index.ts | 2 +- src/core/component/watch/bind.ts | 119 +++++++-------------- src/core/component/watch/create.ts | 36 ++----- src/core/component/watch/helpers.ts | 40 +++++++ src/core/component/watch/index.ts | 4 + 7 files changed, 100 insertions(+), 152 deletions(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index 988bc6516a..cd00320068 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -7,7 +7,7 @@ */ import { isProxy } from 'core/object/watch'; -import { getPropertyInfo, isBinding, isCustomWatcher, PropertyInfo } from 'core/component'; +import { getPropertyInfo, canSkipWatching, isBinding, isCustomWatcher, PropertyInfo } from 'core/component'; import type iBlock from 'components/super/i-block/i-block'; import type Sync from 'components/friends/sync/class'; @@ -391,29 +391,7 @@ export function link( return resolveVal; }; - let canSkipWatching = !resolvedOpts.immediate; - - // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template. - // Also, prop watching does not work during SSR. - if (canSkipWatching && srcInfo != null && (srcInfo.type === 'prop' || srcInfo.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = srcInfo; - - canSkipWatching = SSR || params.root === true || params.functional === true; - - if (!canSkipWatching) { - const - prop = meta.props[srcInfo.name], - propName = prop?.forceUpdate !== false ? srcInfo.name : `on:${srcInfo.name}`; - - canSkipWatching = ctx.getPassedProps?.().has(propName) === false; - } - - } else { - canSkipWatching = false; - } - - if (!canSkipWatching) { + if (!canSkipWatching(srcInfo, resolvedOpts)) { if (getter != null && (getter.length > 1 || getter['originalLength'] > 1)) { ctx.watch(srcInfo ?? normalizedPath, resolvedOpts, (val: unknown, oldVal: unknown, ...args: unknown[]) => { if (customWatcher) { diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index e2eb32d784..80d9b297be 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -36,6 +36,7 @@ import { getComponentName, getPropertyInfo, + canSkipWatching, bindRemoteWatchers, isCustomWatcher, @@ -514,29 +515,7 @@ export default abstract class iBlockBase extends iBlockFriends { info = Object.cast(path); } - let canSkipWatching = !opts.immediate; - - // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template. - // Also, prop watching does not work during SSR. - if (canSkipWatching && info != null && (info.type === 'prop' || info.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = info; - - canSkipWatching = SSR || params.root === true || params.functional === true; - - if (!canSkipWatching) { - const - prop = meta.props[info.name], - propName = prop?.forceUpdate !== false ? info.name : `on:${info.name}`; - - canSkipWatching = ctx.getPassedProps?.().has(propName) === false; - } - - } else { - canSkipWatching = false; - } - - if (canSkipWatching) { + if (canSkipWatching(info, opts)) { return; } diff --git a/src/core/component/index.ts b/src/core/component/index.ts index 3a1ec2b5ef..825e179c10 100644 --- a/src/core/component/index.ts +++ b/src/core/component/index.ts @@ -20,7 +20,7 @@ export type { State } from 'core/component/state'; export { ComponentEngine as default } from 'core/component/engines'; export { runHook } from 'core/component/hook'; -export { bindRemoteWatchers, isCustomWatcher, customWatcherRgxp } from 'core/component/watch'; +export { bindRemoteWatchers, canSkipWatching, isCustomWatcher, customWatcherRgxp } from 'core/component/watch'; export { callMethodFromComponent } from 'core/component/method'; export { normalizeClass, normalizeStyle } from 'core/component/render'; diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index fcdb74c7ab..b7af3f4acc 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -11,6 +11,7 @@ import { getPropertyInfo } from 'core/component/reflect'; import { beforeHooks } from 'core/component/const'; import { isCustomWatcher, customWatcherRgxp } from 'core/component/watch/const'; +import { canSkipWatching } from 'core/component/watch/helpers'; import type { ComponentInterface } from 'core/component/interface'; import type { BindRemoteWatchersParams } from 'core/component/watch/interface'; @@ -283,53 +284,34 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - let - link: Nullable, - unwatch: Nullable; - - const toWatch = p.info ?? getPropertyInfo(watchPath, component); - - let canSkipWatching = !watchInfo.immediate; - - // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template. - // Also, prop watching does not work during SSR. - if (canSkipWatching && (toWatch.type === 'prop' || toWatch.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = toWatch; - - canSkipWatching = SSR || params.root === true || params.functional === true; + const info = p.info ?? getPropertyInfo(watchPath, component); - if (!canSkipWatching) { - const - prop = meta.props[toWatch.name], - propName = prop?.forceUpdate !== false ? toWatch.name : `on:${toWatch.name}`; + if (canSkipWatching(info, watchInfo)) { + return; + } - canSkipWatching = ctx.getPassedProps?.().has(propName) === false; - } + /* eslint-disable prefer-const */ - } else { - canSkipWatching = false; - } + let + link: Nullable, + unwatch: Nullable; - if (canSkipWatching) { - unwatch = () => undefined; + /* eslint-enable prefer-const */ - } else { - const emitter: EventEmitterLikeP = (_, wrappedHandler) => { - handler = Object.cast(wrappedHandler); + const emitter: EventEmitterLikeP = (_, wrappedHandler) => { + handler = Object.cast(wrappedHandler); - $a.worker(() => { - if (link != null) { - $a.off(link); - } - }, asyncParams); + $a.worker(() => { + if (link != null) { + $a.off(link); + } + }, asyncParams); - return () => unwatch?.(); - }; + return () => unwatch?.(); + }; - link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); - unwatch = $watch.call(component, toWatch, watchInfo, handler); - } + link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); + unwatch = $watch.call(component, info, watchInfo, handler); }).catch(stderr); } else { @@ -361,53 +343,34 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - let - link: Nullable, - unwatch: Nullable; - - const toWatch = p.info ?? getPropertyInfo(watchPath, component); + const info = p.info ?? getPropertyInfo(watchPath, component); - let canSkipWatching = !watchInfo.immediate; - - // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template. - // Also, prop watching does not work during SSR. - if (canSkipWatching && (toWatch.type === 'prop' || toWatch.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = toWatch; - - canSkipWatching = SSR || params.root === true || params.functional === true; + if (canSkipWatching(info, watchInfo)) { + return; + } - if (!canSkipWatching) { - const - prop = meta.props[toWatch.name], - propName = prop?.forceUpdate !== false ? toWatch.name : `on:${toWatch.name}`; + /* eslint-disable prefer-const */ - canSkipWatching = ctx.getPassedProps?.().has(propName) === false; - } + let + link: Nullable, + unwatch: Nullable; - } else { - canSkipWatching = false; - } + /* eslint-enable prefer-const */ - if (canSkipWatching) { - unwatch = () => undefined; + const emitter: EventEmitterLikeP = (_, wrappedHandler) => { + handler = Object.cast(wrappedHandler); - } else { - const emitter: EventEmitterLikeP = (_, wrappedHandler) => { - handler = Object.cast(wrappedHandler); - - $a.worker(() => { - if (link != null) { - $a.off(link); - } - }, asyncParams); + $a.worker(() => { + if (link != null) { + $a.off(link); + } + }, asyncParams); - return () => unwatch?.(); - }; + return () => unwatch?.(); + }; - link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); - unwatch = $watch.call(component, toWatch, watchInfo, handler); - } + link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); + unwatch = $watch.call(component, info, watchInfo, handler); } }); } diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index f757758057..745f9dcd16 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -13,7 +13,7 @@ import { getPropertyInfo, isPrivateField, PropertyInfo } from 'core/component/re import { tiedWatchers, watcherInitializer } from 'core/component/watch/const'; import { cloneWatchValue } from 'core/component/watch/clone'; -import { attachDynamicWatcher } from 'core/component/watch/helpers'; +import { attachDynamicWatcher, canSkipWatching } from 'core/component/watch/helpers'; import type { ComponentMeta, ComponentField } from 'core/component/meta'; import type { ComponentInterface, WatchPath, WatchOptions, RawWatchHandler } from 'core/component/interface'; @@ -23,10 +23,8 @@ import type { ComponentInterface, WatchPath, WatchOptions, RawWatchHandler } fro * @param component */ export function createWatchFn(component: ComponentInterface): ComponentInterface['$watch'] { - const - watchCache = new Map(); + const watchCache = new Map(); - /* eslint-disable-next-line complexity */ return function watchFn( this: ComponentInterface, path: WatchPath | object, @@ -87,26 +85,9 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface isFunctional = SSR || !isRoot && ctxParams.functional === true; } - const isDefinedPath = Object.size(info.path) > 0; - - let canSkipWatching = false; - - // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template. - // Also, prop watching does not work during SSR. - if (info.type === 'prop' || info.type === 'attr') { - canSkipWatching = SSR || isRoot || isFunctional; - - if (!canSkipWatching) { - const - prop = info.ctx.meta.props[info.name], - propName = prop?.forceUpdate !== false ? info.name : `on:${info.name}`; - - canSkipWatching = info.ctx.getPassedProps?.().has(propName) === false; - } - } + let skipWatching = canSkipWatching(info); - if (!canSkipWatching && isFunctional) { + if (!skipWatching && isFunctional) { let field: Nullable; switch (info.type) { @@ -123,7 +104,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface } if (field != null) { - canSkipWatching = field.functional === false || field.functionalWatching === false; + skipWatching = field.functional === false || field.functionalWatching === false; } } @@ -146,11 +127,14 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface needImmediate = Boolean(normalizedOpts.immediate), needCache = (handler['originalLength'] ?? handler.length) > 1 && needCollapse; - if (canSkipWatching && !needImmediate) { + if (skipWatching && !needImmediate) { return null; } + const isDefinedPath = Object.size(info.path) > 0; + const {flush} = normalizedOpts; + delete normalizedOpts.flush; normalizedOpts.immediate = flush === 'sync'; @@ -291,7 +275,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface } } - if (canSkipWatching) { + if (skipWatching) { return null; } diff --git a/src/core/component/watch/helpers.ts b/src/core/component/watch/helpers.ts index 35a1f660e3..50aff910b0 100644 --- a/src/core/component/watch/helpers.ts +++ b/src/core/component/watch/helpers.ts @@ -14,6 +14,46 @@ import { dynamicHandlers } from 'core/component/watch/const'; import type { ComponentInterface } from 'core/component/interface'; import type { DynamicHandlers } from 'core/component/watch/interface'; +/** + * Returns true if initialization of observation for the given property can be skipped. + * For example, if it is a prop of a functional component or a prop that was not passed in the template, etc. + * + * @param property - the property information object + * @param [opts] - additional observation options + */ +export function canSkipWatching( + property: Nullable, + opts?: Nullable +): boolean { + if (property == null) { + return false; + } + + let canSkipWatching = opts?.immediate !== true; + + // We cannot observe props and attributes on a component if it is a root component, a functional component, + // or if it does not accept such parameters in the template. + // Also, prop watching does not work during SSR. + if (canSkipWatching && (property.type === 'prop' || property.type === 'attr')) { + const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = property; + + canSkipWatching = SSR || params.root === true || params.functional === true; + + if (!canSkipWatching) { + const + prop = meta.props[property.name], + propName = prop?.forceUpdate !== false ? property.name : `on:${property.name}`; + + canSkipWatching = ctx.getPassedProps?.().has(propName) === false; + } + + } else { + canSkipWatching = false; + } + + return canSkipWatching; +} + /** * Attaches a dynamic watcher to the specified property. * This function is used to manage a situation when we are watching some accessor. diff --git a/src/core/component/watch/index.ts b/src/core/component/watch/index.ts index e6a165a7f0..996ba07149 100644 --- a/src/core/component/watch/index.ts +++ b/src/core/component/watch/index.ts @@ -12,8 +12,12 @@ */ export * from 'core/component/watch/const'; +export { canSkipWatching } from 'core/component/watch/helpers'; + export * from 'core/component/watch/bind'; export * from 'core/component/watch/clone'; + export * from 'core/component/watch/component-api'; export * from 'core/component/watch/create'; + export * from 'core/component/watch/interface'; From 67727816ecae117bc6f451907c06cf46a64daf4a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 26 Sep 2024 15:07:28 +0300 Subject: [PATCH 158/334] chore: fixed eslint --- src/components/friends/sync/link.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/friends/sync/link.ts b/src/components/friends/sync/link.ts index cd00320068..976939371e 100644 --- a/src/components/friends/sync/link.ts +++ b/src/components/friends/sync/link.ts @@ -247,7 +247,6 @@ export function link( getter?: LinkGetter ): CanUndef; -// eslint-disable-next-line complexity export function link( this: Sync, path?: LinkDecl | AsyncWatchOptions | LinkGetter, From 4d0b31f78094c24227d49e67d23c4e55aeee5d91 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 26 Sep 2024 15:09:29 +0300 Subject: [PATCH 159/334] chore: respect `functional: false` & `functionalWatching: false` --- src/core/component/watch/create.ts | 25 +----------- src/core/component/watch/helpers.ts | 60 ++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 745f9dcd16..33b9ce3189 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -15,7 +15,7 @@ import { tiedWatchers, watcherInitializer } from 'core/component/watch/const'; import { cloneWatchValue } from 'core/component/watch/clone'; import { attachDynamicWatcher, canSkipWatching } from 'core/component/watch/helpers'; -import type { ComponentMeta, ComponentField } from 'core/component/meta'; +import type { ComponentMeta } from 'core/component/meta'; import type { ComponentInterface, WatchPath, WatchOptions, RawWatchHandler } from 'core/component/interface'; /** @@ -85,28 +85,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface isFunctional = SSR || !isRoot && ctxParams.functional === true; } - let skipWatching = canSkipWatching(info); - - if (!skipWatching && isFunctional) { - let field: Nullable; - - switch (info.type) { - case 'system': - field = meta?.systemFields[info.name]; - break; - - case 'field': - field = meta?.fields[info.name]; - break; - - default: - // Do nothing - } - - if (field != null) { - skipWatching = field.functional === false || field.functionalWatching === false; - } - } + const skipWatching = canSkipWatching(info); const isAccessor = Boolean(info.type === 'accessor' || info.type === 'computed' || info.accessor), diff --git a/src/core/component/watch/helpers.ts b/src/core/component/watch/helpers.ts index 50aff910b0..ce6eeee23d 100644 --- a/src/core/component/watch/helpers.ts +++ b/src/core/component/watch/helpers.ts @@ -11,47 +11,69 @@ import watch, { Watcher, WatchOptions, MultipleWatchHandler } from 'core/object/ import type { PropertyInfo } from 'core/component/reflect'; import { dynamicHandlers } from 'core/component/watch/const'; -import type { ComponentInterface } from 'core/component/interface'; +import type { ComponentInterface, ComponentField } from 'core/component/interface'; import type { DynamicHandlers } from 'core/component/watch/interface'; /** * Returns true if initialization of observation for the given property can be skipped. * For example, if it is a prop of a functional component or a prop that was not passed in the template, etc. * - * @param property - the property information object + * @param propInfo - the property information object * @param [opts] - additional observation options */ export function canSkipWatching( - property: Nullable, + propInfo: Nullable, opts?: Nullable ): boolean { - if (property == null) { + if (propInfo == null || propInfo.type === 'mounted') { return false; } - let canSkipWatching = opts?.immediate !== true; + let skipWatching = opts?.immediate !== true; - // We cannot observe props and attributes on a component if it is a root component, a functional component, - // or if it does not accept such parameters in the template. - // Also, prop watching does not work during SSR. - if (canSkipWatching && (property.type === 'prop' || property.type === 'attr')) { - const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = property; + if (skipWatching) { + const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = propInfo; - canSkipWatching = SSR || params.root === true || params.functional === true; + const isFunctional = params.functional === true; - if (!canSkipWatching) { - const - prop = meta.props[property.name], - propName = prop?.forceUpdate !== false ? property.name : `on:${property.name}`; + if (propInfo.type === 'prop' || propInfo.type === 'attr') { + skipWatching = SSR || params.root === true || isFunctional; + + if (!skipWatching) { + const + prop = meta.props[propInfo.name], + propName = prop?.forceUpdate !== false ? propInfo.name : `on:${propInfo.name}`; + + skipWatching = ctx.getPassedProps?.().has(propName) === false; + } - canSkipWatching = ctx.getPassedProps?.().has(propName) === false; + } else { + skipWatching = false; } - } else { - canSkipWatching = false; + if (!skipWatching && isFunctional) { + let field: Nullable; + + switch (propInfo.type) { + case 'system': + field = meta.systemFields[propInfo.name]; + break; + + case 'field': + field = meta.fields[propInfo.name]; + break; + + default: + // Do nothing + } + + if (field != null) { + skipWatching = field.functional === false || field.functionalWatching === false; + } + } } - return canSkipWatching; + return skipWatching; } /** From 0148ad457198a03e9861ade734a3f01ea96a78fb Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 26 Sep 2024 15:20:48 +0300 Subject: [PATCH 160/334] chore: better naming --- src/core/component/watch/bind.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index b7af3f4acc..d1cc17ed43 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -284,9 +284,9 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - const info = p.info ?? getPropertyInfo(watchPath, component); + const propInfo = p.info ?? getPropertyInfo(watchPath, component); - if (canSkipWatching(info, watchInfo)) { + if (canSkipWatching(propInfo, watchInfo)) { return; } @@ -311,7 +311,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR }; link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); - unwatch = $watch.call(component, info, watchInfo, handler); + unwatch = $watch.call(component, propInfo, watchInfo, handler); }).catch(stderr); } else { @@ -343,9 +343,9 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - const info = p.info ?? getPropertyInfo(watchPath, component); + const propInfo = p.info ?? getPropertyInfo(watchPath, component); - if (canSkipWatching(info, watchInfo)) { + if (canSkipWatching(propInfo, watchInfo)) { return; } @@ -370,7 +370,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR }; link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); - unwatch = $watch.call(component, info, watchInfo, handler); + unwatch = $watch.call(component, propInfo, watchInfo, handler); } }); } From eedadf2dc9582bdd440e1744c7ec2d971111d721 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 26 Sep 2024 15:48:54 +0300 Subject: [PATCH 161/334] fix: fixed check --- src/core/component/watch/helpers.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/component/watch/helpers.ts b/src/core/component/watch/helpers.ts index ce6eeee23d..e4cd72b079 100644 --- a/src/core/component/watch/helpers.ts +++ b/src/core/component/watch/helpers.ts @@ -25,7 +25,7 @@ export function canSkipWatching( propInfo: Nullable, opts?: Nullable ): boolean { - if (propInfo == null || propInfo.type === 'mounted') { + if (propInfo == null || !('type' in propInfo) || propInfo.type === 'mounted') { return false; } @@ -34,6 +34,11 @@ export function canSkipWatching( if (skipWatching) { const {ctx, ctx: {unsafe: {meta, meta: {params}}}} = propInfo; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (meta == null || params == null) { + return false; + } + const isFunctional = params.functional === true; if (propInfo.type === 'prop' || propInfo.type === 'attr') { From 10374ce7c2ecf14e6bd3f6ca613e4ef0ef24e559 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 26 Sep 2024 16:27:59 +0300 Subject: [PATCH 162/334] chore: optimization --- src/components/super/i-block/mods/index.ts | 10 +--------- src/components/super/i-block/state/index.ts | 11 +++++++++++ src/core/component/render/wrappers.ts | 7 +++++++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/components/super/i-block/mods/index.ts b/src/components/super/i-block/mods/index.ts index 1857d80160..75390ea9f2 100644 --- a/src/components/super/i-block/mods/index.ts +++ b/src/components/super/i-block/mods/index.ts @@ -13,7 +13,7 @@ import { component, PARENT } from 'core/component'; -import { field, system, computed, hook } from 'components/super/i-block/decorators'; +import { field, system, computed } from 'components/super/i-block/decorators'; import { initMods, mergeMods, getReactiveMods, ModsDict, ModsDecl } from 'components/super/i-block/modules/mods'; import type iBlock from 'components/super/i-block/i-block'; @@ -201,12 +201,4 @@ export default abstract class iBlockMods extends iBlockEvent { removeRootMod(name: string, value?: unknown): boolean { return this.r.removeRootMod(name, value, Object.cast(this)); } - - /** - * Initializes modifier event listeners - */ - @hook('beforeCreate') - protected initModEvents(): void { - this.sync.mod('stage', 'stageStore', (v) => v == null ? v : String(v)); - } } diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index d3a8380129..43ff23c4b9 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -633,6 +633,17 @@ export default abstract class iBlockState extends iBlockMods { } } + /** + * Initializes modifier event listeners + */ + @hook('beforeCreate') + protected initModEvents(): void { + this.sync.mod('stage', ':stageChange', {immediate: true}, () => { + const v = this.stage; + return v == null ? v : String(v); + }); + } + /** * Hook handler: the component is preparing to be destroyed */ diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 0f396fd9f7..6d5ae5c6f7 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -124,6 +124,13 @@ export function wrapCreateBlock(original: T): T { vnode.props ??= {}; + // By default, mods are passed down from the parent (see `sharedMods`), but if there is actually nothing there, + // we remove them from the vnode to avoid registering empty handlers and watchers + if (vnode.props.modsProp == null) { + delete vnode.props.modsProp; + delete vnode.props['on:modsProp']; + } + vnode.props.getRoot ??= this.$getRoot(this); vnode.props.getParent ??= () => vnode.virtualParent?.value != null ? From ae3b78fcfdbaed9fa9c84b7dee6c45bb85eb2941 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 26 Sep 2024 16:40:24 +0300 Subject: [PATCH 163/334] fix: fixed `modsProp` optimization --- src/core/component/render/wrappers.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 6d5ae5c6f7..a0cbf8fb7e 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -122,23 +122,24 @@ export function wrapCreateBlock(original: T): T { isRegular = params.functional !== true, vnode = createVNode(name, attrs, isRegular ? slots : [], patchFlag, dynamicProps); - vnode.props ??= {}; + let props = vnode.props ?? {}; + vnode.props = props; // By default, mods are passed down from the parent (see `sharedMods`), but if there is actually nothing there, // we remove them from the vnode to avoid registering empty handlers and watchers - if (vnode.props.modsProp == null) { + if ('modsProp' in props && props.modsProp == null) { delete vnode.props.modsProp; delete vnode.props['on:modsProp']; } - vnode.props.getRoot ??= this.$getRoot(this); + props.getRoot ??= this.$getRoot(this); - vnode.props.getParent ??= () => vnode.virtualParent?.value != null ? + props.getParent ??= () => vnode.virtualParent?.value != null ? vnode.virtualParent.value : this; let passedProps: CanNull> = null; - vnode.props.getPassedProps ??= () => passedProps ??= new Set(attrs != null ? Object.keys(attrs) : []); + props.getPassedProps ??= () => passedProps ??= new Set(attrs != null ? Object.keys(attrs) : []); // For refs within functional components, // it is necessary to explicitly set a reference to the instance of the component @@ -164,7 +165,7 @@ export function wrapCreateBlock(original: T): T { declaredProps = component.props, filteredAttrs = {}; - Object.entries(vnode.props).forEach(([key, val]) => { + Object.entries(props).forEach(([key, val]) => { if (declaredProps[key.camelize(false)] == null) { filteredAttrs[key] = val; } @@ -173,7 +174,9 @@ export function wrapCreateBlock(original: T): T { const functionalVNode = virtualCtx.render(virtualCtx, []); vnode.type = functionalVNode.type; - vnode.props = merge(filteredAttrs, functionalVNode.props ?? {}); + + props = merge(filteredAttrs, functionalVNode.props ?? {}); + vnode.props = props; vnode.children = functionalVNode.children; vnode.dynamicChildren = functionalVNode.dynamicChildren; From 135e8f518adfada4fafb99b093ba04d89456f2b2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 19:26:36 +0300 Subject: [PATCH 164/334] chore: stylish fixes --- src/core/component/method/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/core/component/method/index.ts b/src/core/component/method/index.ts index d04d8575c6..450e209782 100644 --- a/src/core/component/method/index.ts +++ b/src/core/component/method/index.ts @@ -18,10 +18,7 @@ import type { ComponentInterface } from 'core/component/interface'; * @param component */ export function attachMethodsFromMeta(component: ComponentInterface): void { - const { - meta, - meta: {methods} - } = component.unsafe; + const {meta, meta: {methods}} = component.unsafe; const isFunctional = meta.params.functional === true; From 86f34265c1c42a438803c07333c46c9821a42950 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 19:41:59 +0300 Subject: [PATCH 165/334] chore: refactoring & optimization --- src/components/super/i-block/base/index.ts | 32 ++++- src/core/component/watch/bind.ts | 160 +++++++++++---------- 2 files changed, 111 insertions(+), 81 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 80d9b297be..e8e21f4ce4 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -40,8 +40,10 @@ import { bindRemoteWatchers, isCustomWatcher, - RawWatchHandler, + PropertyInfo, + WatchPath, + RawWatchHandler, SetupContext @@ -500,13 +502,24 @@ export default abstract class iBlockBase extends iBlockFriends { return; } - if (this.lfc.isBeforeCreate()) { - hooks['before:created'].push({fn: initWatcher}); + let info: CanNull; + + if (this.hook === 'beforeDataCreate') { + if (!canSkipWatching(getInfo(), opts)) { + attachWatcher(); + } + + } else if (this.lfc.isBeforeCreate()) { + attachWatcher(); } else { initWatcher(); } + function attachWatcher() { + hooks['before:created'].push({fn: initWatcher}); + } + function initWatcher() { let info = Object.isString(path) ? getPropertyInfo(path, that) : null; @@ -540,7 +553,18 @@ export default abstract class iBlockBase extends iBlockFriends { }; link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(opts, 'watchers')); - unwatch = that.$watch(Object.cast(path), opts, handler); + unwatch = that.$watch(info ?? Object.cast(path), opts, handler); + } + + function getInfo() { + info ??= Object.isString(path) ? getPropertyInfo(path, that) : null; + + // TODO: Implement a more accurate check + if (info == null && !isProxy(path)) { + info = Object.cast(path); + } + + return info; } } diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index d1cc17ed43..570927d631 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -46,51 +46,75 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR isDeactivated = hook === 'deactivated', // True if the component has not been created yet - isBeforeCreate = Boolean(beforeHooks[hook]); - - const - watchersMap = p.watchers ?? meta.watchers, + isBeforeCreate = Boolean(beforeHooks[hook]), // True if the method has been invoked with passing the custom async instance as a property - customAsync = $a !== unsafe.$async; + isCustomAsync = $a !== unsafe.$async; + + const watchers = p.watchers ?? meta.watchers; // Iterate over all registered watchers and listeners and initialize their - Object.entries(watchersMap).forEach(([watchPath, watchers]) => { + Object.entries(watchers).forEach(([watchPath, watchers]) => { if (watchers == null) { return; } - let - // A link to the context of the watcher; - // by default, a component is passed to the function - watcherCtx = component, + // A link to the context of the watcher; + // by default, a component is passed to the function + let watcherCtx = component; - // True if this watcher can initialize only when the component is created - watcherNeedCreated = true, + // True if this watcher can initialize only when the component is created + let attachWatcherOnCreated = true; - // True if this watcher can initialize only when the component is mounted - watcherNeedMounted = false; + // True if this watcher can initialize only when the component is mounted + let attachWatcherOnMounted = false; // Custom watchers look like ':foo', 'bla:foo', '?bla:foo' - // and are used to listen to custom events instead of property mutations. + // and are used to listen to custom events instead of property mutations const customWatcher = isCustomWatcher.test(watchPath) ? customWatcherRgxp.exec(watchPath) : null; + // True if the watcher is handling the component's event + const isSelfComponentEvent = customWatcher != null && customWatcher[2] === ''; + if (customWatcher != null) { - const m = customWatcher[1]; - watcherNeedCreated = m === ''; - watcherNeedMounted = m === '?'; + const hookMod = customWatcher[1]; + attachWatcherOnCreated = hookMod === ''; + attachWatcherOnMounted = hookMod === '?'; } - // Add a listener to a component's created hook if the component has not yet been created - if (watcherNeedCreated && isBeforeCreate) { - hooks['before:created'].push({fn: attachWatcher}); - return; - } + // Add a listener to the component's created hook if the component has not been created yet + if (attachWatcherOnCreated && isBeforeCreate) { + // Optimization of the most common use case + if (hook === 'beforeDataCreate' && watchers.length === 1) { + const watchInfo = watchers[0]; + + if (watchInfo.shouldInit?.(component) === false) { + return; + } + + if (customWatcher == null) { + const propInfo = p.info ?? getPropertyInfo(watchPath, component); - // Add a listener to a component's mounted/activated hook if the component has not yet been mounted or activated - if (watcherNeedMounted && (isBeforeCreate || component.$el == null)) { + if (canSkipWatching(propInfo, watchInfo)) { + return; + } + } + } + + // Such events can be registered before the created hook + if (isSelfComponentEvent) { + attachWatcher(); + + } else { + hooks['before:created'].push({fn: attachWatcher}); + } + + // Add a listener to the component's mounted/activated hook if the component has not been mounted or activated yet + } else if (attachWatcherOnMounted && (isBeforeCreate || component.$el == null)) { hooks[isDeactivated ? 'activated' : 'mounted'].unshift({fn: attachWatcher}); - return; + + } else { + attachWatcher(); } attachWatcher(); @@ -101,10 +125,10 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // ':foo' -> watcherCtx == ctx; key = 'foo' // 'document:foo' -> watcherCtx == document; key = 'foo' if (customWatcher != null) { - const l = customWatcher[2]; + const pathToEmitter = customWatcher[2]; - if (l !== '') { - watcherCtx = Object.get(component, l) ?? Object.get(globalThis, l) ?? component; + if (pathToEmitter !== '') { + watcherCtx = Object.get(component, pathToEmitter) ?? Object.get(globalThis, pathToEmitter) ?? component; watchPath = customWatcher[3].toString(); } else { @@ -119,6 +143,16 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } + let propInfo = p.info; + + if (customWatcher == null) { + propInfo ??= getPropertyInfo(watchPath, component); + + if (canSkipWatching(propInfo, watchInfo)) { + return; + } + } + const rawHandler = watchInfo.handler; const asyncParams = { @@ -127,7 +161,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR join: watchInfo.join }; - if (!customAsync) { + if (!isCustomAsync) { if (asyncParams.label == null && !watchInfo.immediate) { let defLabel: string; @@ -257,24 +291,22 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR } if (customWatcher) { - // True if the component itself can listen to an event, - // because watcherCtx does not appear to be an event emitter - const needDefEmitter = + const needUse$on = watcherCtx === component && - !Object.isFunction(watcherCtx['on']) && - !Object.isFunction(watcherCtx['addListener']); + !('on' in watcherCtx) && + !('addListener' in watcherCtx); - if (needDefEmitter) { + if (needUse$on) { unsafe.$on(watchPath, handler); } else { - const addListener = (watcherCtx: ComponentInterface & EventEmitterLike) => + const addListener = (watcherCtx: ComponentInterface & EventEmitterLike) => { $a.on(watcherCtx, watchPath, handler, eventParams, ...watchInfo.args ?? []); + }; if (Object.isPromise(watcherCtx)) { - $a.promise(Object.cast(watcherCtx), asyncParams) - .then(addListener) - .catch(stderr); + type PromiseType = ComponentInterface & EventEmitterLike; + $a.promise(Object.cast(watcherCtx), asyncParams).then(addListener).catch(stderr); } else { addListener(watcherCtx); @@ -284,12 +316,6 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - const propInfo = p.info ?? getPropertyInfo(watchPath, component); - - if (canSkipWatching(propInfo, watchInfo)) { - return; - } - /* eslint-disable prefer-const */ let @@ -299,14 +325,8 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR /* eslint-enable prefer-const */ const emitter: EventEmitterLikeP = (_, wrappedHandler) => { - handler = Object.cast(wrappedHandler); - - $a.worker(() => { - if (link != null) { - $a.off(link); - } - }, asyncParams); - + handler = wrappedHandler; + $a.worker(() => link != null && $a.off(link), asyncParams); return () => unwatch?.(); }; @@ -316,24 +336,22 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR } else { if (customWatcher) { - // True if the component itself can listen to an event, - // because watcherCtx does not appear to be an event emitter - const needDefEmitter = + const needUse$on = watcherCtx === component && - !Object.isFunction(watcherCtx['on']) && - !Object.isFunction(watcherCtx['addListener']); + !('on' in watcherCtx) && + !('addListener' in watcherCtx); - if (needDefEmitter) { + if (needUse$on) { unsafe.$on(watchPath, handler); } else { - const addListener = (watcherCtx: ComponentInterface & EventEmitterLike) => + const addListener = (watcherCtx: ComponentInterface & EventEmitterLike) => { $a.on(watcherCtx, watchPath, handler, eventParams, ...watchInfo.args ?? []); + }; if (Object.isPromise(watcherCtx)) { - $a.promise(Object.cast(watcherCtx), asyncParams) - .then(addListener) - .catch(stderr); + type PromiseType = ComponentInterface & EventEmitterLike; + $a.promise(Object.cast(watcherCtx), asyncParams).then(addListener).catch(stderr); } else { addListener(watcherCtx); @@ -343,12 +361,6 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - const propInfo = p.info ?? getPropertyInfo(watchPath, component); - - if (canSkipWatching(propInfo, watchInfo)) { - return; - } - /* eslint-disable prefer-const */ let @@ -359,13 +371,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR const emitter: EventEmitterLikeP = (_, wrappedHandler) => { handler = Object.cast(wrappedHandler); - - $a.worker(() => { - if (link != null) { - $a.off(link); - } - }, asyncParams); - + $a.worker(() => link != null && $a.off(link), asyncParams); return () => unwatch?.(); }; From a9df80aca63feab616a8c57852fd6607a38e5c22 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 19:42:58 +0300 Subject: [PATCH 166/334] chore: removed redundant code --- src/core/component/watch/bind.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 570927d631..92a267f75e 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -37,9 +37,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR {unsafe} = component, {$watch, meta, hook, meta: {hooks}} = unsafe; - const - componentAsync = unsafe.$async, - $a = p.async ?? componentAsync; + const $a = p.async ?? unsafe.$async; const // True if the component is currently deactivated From 7ad9a731fdc97fc98a6c8535e7babf37e09576aa Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 19:55:28 +0300 Subject: [PATCH 167/334] chore: refactoring --- src/components/super/i-block/base/index.ts | 18 +++--------------- src/core/component/watch/bind.ts | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index e8e21f4ce4..c8de78ad83 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -502,7 +502,7 @@ export default abstract class iBlockBase extends iBlockFriends { return; } - let info: CanNull; + let info: CanNull = null; if (this.hook === 'beforeDataCreate') { if (!canSkipWatching(getInfo(), opts)) { @@ -521,14 +521,7 @@ export default abstract class iBlockBase extends iBlockFriends { } function initWatcher() { - let info = Object.isString(path) ? getPropertyInfo(path, that) : null; - - // TODO: Implement a more accurate check - if (info == null && !isProxy(path)) { - info = Object.cast(path); - } - - if (canSkipWatching(info, opts)) { + if (info == null && canSkipWatching(getInfo(), opts)) { return; } @@ -543,12 +536,7 @@ export default abstract class iBlockBase extends iBlockFriends { wrappedHandler['originalLength'] = handler['originalLength'] ?? handler.length; handler = wrappedHandler; - $a.worker(() => { - if (link != null) { - $a.off(link); - } - }, opts); - + $a.worker(() => link != null && $a.off(link), opts); return () => unwatch?.(); }; diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 92a267f75e..7363781cce 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -93,9 +93,11 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR if (customWatcher == null) { const propInfo = p.info ?? getPropertyInfo(watchPath, component); - if (canSkipWatching(propInfo, watchInfo)) { - return; + if (!canSkipWatching(propInfo, watchInfo)) { + hooks['before:created'].push({fn: attachWatcher.bind(null, propInfo)}); } + + return; } } @@ -107,17 +109,18 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR hooks['before:created'].push({fn: attachWatcher}); } + return; + } + // Add a listener to the component's mounted/activated hook if the component has not been mounted or activated yet - } else if (attachWatcherOnMounted && (isBeforeCreate || component.$el == null)) { + if (attachWatcherOnMounted && (isBeforeCreate || component.$el == null)) { hooks[isDeactivated ? 'activated' : 'mounted'].unshift({fn: attachWatcher}); - - } else { - attachWatcher(); + return; } attachWatcher(); - function attachWatcher() { + function attachWatcher(propInfo: typeof p.info = p.info) { // If we have a custom watcher, we need to find a link to the event emitter. // For instance: // ':foo' -> watcherCtx == ctx; key = 'foo' @@ -141,8 +144,6 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - let propInfo = p.info; - if (customWatcher == null) { propInfo ??= getPropertyInfo(watchPath, component); From c43cdb7fc6a3e2355019411103f81ceda1928414 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 19:55:58 +0300 Subject: [PATCH 168/334] chore: stylish fixes --- src/core/component/watch/bind.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 7363781cce..4786ebbd8a 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -155,8 +155,8 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR const rawHandler = watchInfo.handler; const asyncParams = { - label: watchInfo.label, group: watchInfo.group, + label: watchInfo.label, join: watchInfo.join }; From f7f2042e485b4e1723bb28c532da465b7c5a7d0b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 19:59:37 +0300 Subject: [PATCH 169/334] chore: removed redundant optimizations --- src/components/super/i-block/base/index.ts | 33 +++++--------------- src/core/component/watch/bind.ts | 35 +++------------------- 2 files changed, 11 insertions(+), 57 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index c8de78ad83..d775f03521 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -502,27 +502,19 @@ export default abstract class iBlockBase extends iBlockFriends { return; } - let info: CanNull = null; - - if (this.hook === 'beforeDataCreate') { - if (!canSkipWatching(getInfo(), opts)) { - attachWatcher(); - } - - } else if (this.lfc.isBeforeCreate()) { - attachWatcher(); + if (this.lfc.isBeforeCreate()) { + hooks['before:created'].push({fn: initWatcher}); } else { initWatcher(); } - function attachWatcher() { - hooks['before:created'].push({fn: initWatcher}); - } - function initWatcher() { - if (info == null && canSkipWatching(getInfo(), opts)) { - return; + let info = Object.isString(path) ? getPropertyInfo(path, that) : null; + + // TODO: Implement a more accurate check + if (info == null && !isProxy(path)) { + info = Object.cast(path); } let @@ -543,17 +535,6 @@ export default abstract class iBlockBase extends iBlockFriends { link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(opts, 'watchers')); unwatch = that.$watch(info ?? Object.cast(path), opts, handler); } - - function getInfo() { - info ??= Object.isString(path) ? getPropertyInfo(path, that) : null; - - // TODO: Implement a more accurate check - if (info == null && !isProxy(path)) { - info = Object.cast(path); - } - - return info; - } } /** diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 4786ebbd8a..b349961f3a 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -71,9 +71,6 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // and are used to listen to custom events instead of property mutations const customWatcher = isCustomWatcher.test(watchPath) ? customWatcherRgxp.exec(watchPath) : null; - // True if the watcher is handling the component's event - const isSelfComponentEvent = customWatcher != null && customWatcher[2] === ''; - if (customWatcher != null) { const hookMod = customWatcher[1]; attachWatcherOnCreated = hookMod === ''; @@ -82,33 +79,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // Add a listener to the component's created hook if the component has not been created yet if (attachWatcherOnCreated && isBeforeCreate) { - // Optimization of the most common use case - if (hook === 'beforeDataCreate' && watchers.length === 1) { - const watchInfo = watchers[0]; - - if (watchInfo.shouldInit?.(component) === false) { - return; - } - - if (customWatcher == null) { - const propInfo = p.info ?? getPropertyInfo(watchPath, component); - - if (!canSkipWatching(propInfo, watchInfo)) { - hooks['before:created'].push({fn: attachWatcher.bind(null, propInfo)}); - } - - return; - } - } - - // Such events can be registered before the created hook - if (isSelfComponentEvent) { - attachWatcher(); - - } else { - hooks['before:created'].push({fn: attachWatcher}); - } - + hooks['before:created'].push({fn: attachWatcher}); return; } @@ -120,7 +91,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR attachWatcher(); - function attachWatcher(propInfo: typeof p.info = p.info) { + function attachWatcher() { // If we have a custom watcher, we need to find a link to the event emitter. // For instance: // ':foo' -> watcherCtx == ctx; key = 'foo' @@ -138,6 +109,8 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR } } + let propInfo: typeof p.info = p.info; + // Iterates over all registered handlers for this watcher watchers!.forEach((watchInfo) => { if (watchInfo.shouldInit?.(component) === false) { From 20e5500d48a1a34be51aa0576fda3941a93aa55d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 20:01:06 +0300 Subject: [PATCH 170/334] chore: stylish fixes --- src/core/component/meta/interface/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/meta/interface/index.ts b/src/core/component/meta/interface/index.ts index ad5459d07a..4de8d08595 100644 --- a/src/core/component/meta/interface/index.ts +++ b/src/core/component/meta/interface/index.ts @@ -126,7 +126,7 @@ export interface ComponentMeta { methods: Dictionary; /** - * A dictionary with the component watchers + * A dictionary containing the component watchers */ watchers: Dictionary; From 76d47bbc1020b248625ad5cd31f1bcf6c74dde11 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 20:02:42 +0300 Subject: [PATCH 171/334] chore: stylish fixes --- src/core/component/meta/interface/index.ts | 10 +++++----- src/core/component/meta/interface/types.ts | 3 --- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/core/component/meta/interface/index.ts b/src/core/component/meta/interface/index.ts index 4de8d08595..1bae1cdf63 100644 --- a/src/core/component/meta/interface/index.ts +++ b/src/core/component/meta/interface/index.ts @@ -6,6 +6,8 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import type { WatchPath } from 'core/object/watch'; + import type { PropOptions } from 'core/component/decorators'; import type { RenderFunction, WritableComputedOptions } from 'core/component/engines'; @@ -21,9 +23,7 @@ import type { ComponentAccessor, ComponentHooks, - ComponentDirectiveOptions, - ComponentWatchDependencies, - ComponentWatchPropDependencies + ComponentDirectiveOptions } from 'core/component/meta/interface/types'; @@ -133,12 +133,12 @@ export interface ComponentMeta { /** * A dictionary containing the component dependencies to watch to invalidate the cache of computed fields */ - watchDependencies: ComponentWatchDependencies; + watchDependencies: Map; /** * A dictionary containing the component prop dependencies to watch to invalidate the cache of computed fields */ - watchPropDependencies: ComponentWatchPropDependencies; + watchPropDependencies: Map>; /** * A dictionary containing the component hook listeners, diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index d43e616e66..d591a2fd2f 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -94,6 +94,3 @@ export type ComponentMethodHooks = { }; export interface ComponentDirectiveOptions extends DirectiveBinding {} - -export type ComponentWatchDependencies = Map; -export type ComponentWatchPropDependencies = Map>; From d41d9b5b499a03f643817a356321667619d7e7ac Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 27 Sep 2024 20:08:55 +0300 Subject: [PATCH 172/334] chore: stylish fixes --- src/core/component/meta/fill.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 26be73be5a..2590262ac1 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -204,8 +204,8 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor } const watcherListeners = watchers[fieldName] ?? []; - watchers[fieldName] = watcherListeners; + watcherListeners.push(watcher); }); }); From ab3ac2d359d58f23bcb59dc6e040845979ff007e Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 30 Sep 2024 14:12:50 +0300 Subject: [PATCH 173/334] chore: fixed eslint --- src/components/super/i-block/base/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index d775f03521..86a76d6ad1 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -36,12 +36,9 @@ import { getComponentName, getPropertyInfo, - canSkipWatching, bindRemoteWatchers, isCustomWatcher, - PropertyInfo, - WatchPath, RawWatchHandler, From d0cf66d7ef6d0675d6724d9ad41ab89b25558497 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 30 Sep 2024 14:34:50 +0300 Subject: [PATCH 174/334] chore: optimization --- src/components/form/b-button/b-button.ts | 28 +++++++++++----------- src/components/super/i-block/base/index.ts | 24 ++++++++----------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/components/form/b-button/b-button.ts b/src/components/form/b-button/b-button.ts index 3773f437af..3b70a02c08 100644 --- a/src/components/form/b-button/b-button.ts +++ b/src/components/form/b-button/b-button.ts @@ -65,8 +65,7 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi */ @computed({dependencies: ['type', 'form', 'href', 'hasDropdown']}) get attrs(): Dictionary { - const - attrs = {...this.attrsProp}; + const attrs = {...this.attrsProp}; if (this.type === 'link') { attrs.href = this.href; @@ -87,8 +86,7 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi /** {@link iAccess.prototype.isFocused} */ @computed({dependencies: ['mods.focused']}) get isFocused(): boolean { - const - {button} = this.$refs; + const {button} = this.$refs; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (button != null) { @@ -141,8 +139,7 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi */ @wait('ready') reset(): CanPromise { - const - {file} = this.$refs; + const {file} = this.$refs; if (file != null) { file.value = ''; @@ -155,16 +152,23 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi iOpenToggle.initCloseHelpers(this, events); } + protected override syncDataProviderWatcher(initLoad: boolean = true): void { + if (this.href != null || this.dataProviderProp !== 'Provider') { + super.syncDataProviderWatcher(initLoad); + } + } + protected override initModEvents(): void { - const - {localEmitter: $e} = this; + const {localEmitter: $e} = this; super.initModEvents(); iProgress.initModEvents(this); iProgress.initDisableBehavior(this); + iAccess.initModEvents(this); iOpenToggle.initModEvents(this); + iVisible.initModEvents(this); $e.on('block.mod.*.opened.*', (e: ModEvent) => this.waitComponentStatus('ready', () => { @@ -173,10 +177,7 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi })); $e.on('block.mod.*.disabled.*', (e: ModEvent) => this.waitComponentStatus('ready', () => { - const { - button, - file - } = this.$refs; + const {button, file} = this.$refs; const disabled = e.value !== 'false' && e.type !== 'remove'; button.disabled = disabled; @@ -187,8 +188,7 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi })); $e.on('block.mod.*.focused.*', (e: ModEvent) => this.waitComponentStatus('ready', () => { - const - {button} = this.$refs; + const {button} = this.$refs; if (e.value !== 'false' && e.type !== 'remove') { button.focus(); diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 86a76d6ad1..460be0bbd6 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -127,24 +127,20 @@ export default abstract class iBlockBase extends iBlockFriends { }); } - if (!o.isFunctional) { - o.watch('activatedProp', (val: CanUndef) => { - val = val !== false; + return o.sync.link('activatedProp', (isActivated: CanUndef) => { + isActivated = isActivated !== false; - if (o.hook !== 'beforeDataCreate') { - if (val) { - o.activate(); + if (o.hook !== 'beforeDataCreate') { + if (isActivated) { + o.activate(); - } else { - o.deactivate(); - } + } else { + o.deactivate(); } + } - o.isActivated = val; - }); - } - - return o.activatedProp; + return isActivated; + }); }) isActivated!: boolean; From ae186f7d5694d71456f3bef93264133489e52022 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 30 Sep 2024 16:21:14 +0300 Subject: [PATCH 175/334] chore: optimized accessors initializing --- src/core/component/accessor/index.ts | 238 ++++++++++++------ src/core/component/index.ts | 4 + .../component/init/states/before-create.ts | 84 +------ 3 files changed, 167 insertions(+), 159 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index 8920cf4634..744458f2ae 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -16,6 +16,7 @@ import * as gc from 'core/component/gc'; import { deprecate } from 'core/functools/deprecation'; import { beforeHooks } from 'core/component/const'; +import { getPropertyInfo } from 'core/component/reflect'; import { getFieldsStore } from 'core/component/field'; import { cacheStatus } from 'core/component/watch'; @@ -69,7 +70,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { meta, // eslint-disable-next-line deprecation/deprecation - meta: {params: {deprecatedProps}, fields, tiedFields}, + meta: {params: {deprecatedProps}, tiedFields, hooks}, $destructors } = component.unsafe; @@ -77,35 +78,98 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const isFunctional = meta.params.functional === true; Object.entries(meta.accessors).forEach(([name, accessor]) => { + const tiedWith = tiedFields[name]; + + // In the `tiedFields` dictionary, + // the names of the getters themselves are also stored as keys with their related fields as values. + // This is done for convenience. + // However, watchers for the getter observation of the getter will be created for all keys in `tiedFields`. + // Since it's not possible to watch the getter itself, we need to remove the key with its name. + delete tiedFields[name]; + const canSkip = accessor == null || component[name] != null || !SSR && isFunctional && accessor.functional === false; if (canSkip) { - const tiedWith = tiedFields[name]; - + // If the getter is not initialized, + // then the related fields should also be removed to avoid registering a watcher for the getter observation, + // as it will not be used if (tiedWith != null) { delete tiedFields[tiedWith]; - delete tiedFields[name]; } return; } - delete tiedFields[name]; + let getterInitialized = false; Object.defineProperty(component, name, { configurable: true, enumerable: true, - get: accessor.get, + get: accessor.get != null ? get : undefined, set: accessor.set }); + + function get(this: typeof component): unknown { + if (accessor == null) { + return; + } + + if (!getterInitialized) { + getterInitialized = true; + + const {watchers, watchDependencies} = meta; + + // If a computed property has a field or system field as a dependency + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + watchDependencies.get(name)?.forEach((dep) => { + const + path = Object.isArray(dep) ? dep.join('.') : String(dep), + info = getPropertyInfo(path, component); + + const needToForceWatching = + (info.type === 'system' || info.type === 'field') && + + watchers[info.name] == null && + watchers[info.originalPath] == null && + watchers[info.path] == null; + + if (needToForceWatching) { + watch.call(this, info, {deep: true, immediate: true}, fakeHandler); + } + }); + + if (tiedWith != null) { + const needToForceWatching = watchers[tiedWith] == null && accessor.dependencies?.length !== 0; + + // If a computed property is tied with a field or system field + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + if (needToForceWatching) { + watch.call(this, tiedWith, {deep: true, immediate: true}, fakeHandler); + } + } + } + + return accessor.get!.call(this); + } }); const cachedAccessors = new Set(); Object.entries(meta.computedFields).forEach(([name, computed]) => { + const tiedWith = tiedFields[name]; + + // In the `tiedFields` dictionary, + // the names of the getters themselves are also stored as keys with their related fields as values. + // This is done for convenience. + // However, watchers for cache invalidation of the getter will be created for all keys in `tiedFields`. + // Since it's not possible to watch the getter itself, we need to remove the key with its name. + delete tiedFields[name]; + const canSkip = computed == null || component[name] != null || @@ -113,92 +177,108 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { !SSR && isFunctional && computed.functional === false; if (canSkip) { - const tiedWith = tiedFields[name]; - // If the getter is not initialized, // then the related fields should also be removed to avoid registering a watcher for cache invalidation, // as it will not be used if (tiedWith != null) { delete tiedFields[tiedWith]; - delete tiedFields[name]; } return; } - // In the `tiedFields` dictionary, - // the names of the getters themselves are also stored as keys with their related fields as values. - // This is done for convenience. - // However, watchers for cache invalidation of the getter will be created for all keys in `tiedFields`. - // Since it's not possible to watch the getter itself, we need to remove the key with its name. - delete tiedFields[name]; + const + canUseForeverCache = computed.cache === 'forever', + effects: Function[] = []; - // eslint-disable-next-line func-style - const get = function get(this: typeof component): unknown { - const {unsafe, hook} = this; + let getterInitialized = canUseForeverCache; - const canUseForeverCache = computed.cache === 'forever'; + Object.defineProperty(component, name, { + configurable: true, + enumerable: true, + get: computed.get != null ? get : undefined, + set: computed.set + }); - // We should not use the getter's cache until the component is fully created. - // Because until that moment, we cannot track changes to dependent entities and reset the cache when they change. - // This can lead to hard-to-detect errors. - // Please note that in case of forever caching, we cache immediately. - const canUseCache = canUseForeverCache || beforeHooks[hook] == null; + function get(this: typeof component): unknown { + if (computed == null) { + return; + } - if (canUseCache && cacheStatus in get) { - // If a getter already has a cached result and is used inside a template, - // it is not possible to track its effect, as the value is not recalculated. - // This can lead to a problem where one of the entities on which the getter depends is updated, - // but the template is not. - // To avoid this problem, we explicitly touch all dependent entities. - // For functional components, this problem does not exist, as no change in state can trigger their re-render. - const needEffect = !canUseForeverCache && !isFunctional && hook !== 'created'; - - if (needEffect) { - meta.watchDependencies.get(name)?.forEach((path) => { - let firstChunk: string; - - if (Object.isString(path)) { - if (path.includes('.')) { - const chunks = path.split('.'); - - firstChunk = path[0]; - - if (chunks.length === 1) { - path = firstChunk; - } - - } else { - firstChunk = path; + if (!getterInitialized) { + getterInitialized = true; + + const {watchers, watchDependencies} = meta; + + // If a computed property has a field or system field as a dependency + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + watchDependencies.get(name)?.forEach((dep) => { + const + path = Object.isArray(dep) ? dep.join('.') : String(dep), + info = getPropertyInfo(path, component); + + // If a getter already has a cached result and is used inside a template, + // it is not possible to track its effect, as the value is not recalculated. + // This can lead to a problem where one of the entities on which the getter depends is updated, + // but the template is not. + // To avoid this problem, we explicitly touch all dependent entities. + // For functional components, this problem does not exist, + // as no change in state can trigger their re-render. + if (!isFunctional && info.type !== 'system') { + effects.push(() => { + const store = info.type === 'field' ? getFieldsStore(Object.cast(info.ctx)) : info.ctx; + + if (info.path.includes('.')) { + void Object.get(store, path); + + } else if (path in store) { + // @ts-ignore (effect) + void store[path]; } + }); + } - } else { - firstChunk = path[0]; + const needToForceWatching = + (info.type === 'system' || info.type === 'field') && - if (path.length === 1) { - path = firstChunk; - } - } + watchers[info.name] == null && + watchers[info.originalPath] == null && + watchers[info.path] == null; - const store = fields[firstChunk] != null ? getFieldsStore(unsafe) : unsafe; + if (needToForceWatching) { + watch.call(this, info, {deep: true, immediate: true}, fakeHandler); + } + }); - if (Object.isArray(path)) { - void Object.get(store, path); - - } else if (path in store) { + if (tiedWith != null) { + effects.push(() => { + if (tiedWith in this) { // @ts-ignore (effect) - void store[path]; + void this[tiedWith]; } }); - ['Store', 'Prop'].forEach((postfix) => { - const path = name + postfix; + const needToForceWatching = watchers[tiedWith] == null && computed.dependencies?.length !== 0; - if (path in this) { - // @ts-ignore (effect) - void this[path]; - } - }); + // If a computed property is tied with a field or system field + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + if (needToForceWatching) { + watch.call(this, tiedWith, {deep: true, immediate: true}, fakeHandler); + } + } + } + + // We should not use the getter's cache until the component is fully created. + // Because until that moment, we cannot track changes to dependent entities and reset the cache when they change. + // This can lead to hard-to-detect errors. + // Please note that in case of forever caching, we cache immediately. + const canUseCache = canUseForeverCache || beforeHooks[this.hook] == null; + + if (canUseCache && cacheStatus in get) { + if (this.hook !== 'created') { + effects.forEach((applyEffect) => applyEffect()); } return get[cacheStatus]; @@ -212,14 +292,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { } return value; - }; - - Object.defineProperty(component, name, { - configurable: true, - enumerable: true, - get: computed.get != null ? get : undefined, - set: computed.set - }); + } }); // Register a worker to clean up memory upon component destruction @@ -255,4 +328,17 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { }); }); } + + function fakeHandler() { + // Loopback + } + + function watch(this: typeof component, ...args: Parameters) { + if (beforeHooks[this.hook] != null) { + hooks['before:created'].push({fn: () => this.$watch(...args)}); + + } else { + this.$watch(...args); + } + } } diff --git a/src/core/component/index.ts b/src/core/component/index.ts index 825e179c10..0f82944cbb 100644 --- a/src/core/component/index.ts +++ b/src/core/component/index.ts @@ -32,6 +32,10 @@ export { isComponent, rootComponents, + beforeHooks, + beforeRenderHooks, + beforeMountHooks, + V4_COMPONENT, ASYNC_RENDER_ID, PARENT diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index d88aa62b84..31f813a8a6 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -15,7 +15,6 @@ import { V4_COMPONENT } from 'core/component/const'; import { getComponentContext } from 'core/component/context'; import { forkMeta } from 'core/component/meta'; -import { getPropertyInfo, PropertyInfo } from 'core/component/reflect'; import { getNormalParent } from 'core/component/traverse'; import { initProps, attachAttrPropsListeners } from 'core/component/prop'; @@ -51,8 +50,6 @@ export function beforeCreateState( ): void { meta = forkMeta(meta); - const isFunctional = meta.params.functional === true; - // To avoid TS errors marks all properties as editable const unsafe = Object.cast>(component); unsafe[V4_COMPONENT] = true; @@ -288,86 +285,7 @@ export function beforeCreateState( runHook('beforeRuntime', component).catch(stderr); - const { - systemFields, - tiedFields, - - computedFields, - accessors, - - watchDependencies, - watchers - } = meta; - - initFields(systemFields, component, unsafe); - - const fakeHandler = () => undefined; - - if (watchDependencies.size > 0) { - const watchSet = new Set(); - - watchDependencies.forEach((deps) => { - deps.forEach((dep) => { - const - path = Object.isArray(dep) ? dep.join('.') : String(dep), - info = getPropertyInfo(path, component); - - if (info.type === 'system' || isFunctional && info.type === 'field') { - watchSet.add(info); - } - }); - }); - - // If a computed property has a field or system field as a dependency - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - watchSet.forEach((info) => { - const needToForceWatching = - watchers[info.name] == null && - watchers[info.originalPath] == null && - watchers[info.path] == null; - - if (needToForceWatching) { - watchers[info.name] = [ - { - deep: true, - immediate: true, - provideArgs: false, - handler: fakeHandler - } - ]; - } - }); - } - - // If a computed property is tied with a field or system field - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - Object.entries(tiedFields).forEach(([name, normalizedName]) => { - if (normalizedName == null) { - return; - } - - const - accessor = accessors[normalizedName], - computed = accessor == null ? computedFields[normalizedName] : null; - - const needForceWatch = watchers[name] == null && ( - accessor != null && accessor.dependencies?.length !== 0 || - computed != null && computed.cache !== 'forever' && computed.dependencies?.length !== 0 - ); - - if (needForceWatch) { - watchers[name] = [ - { - deep: true, - immediate: true, - provideArgs: false, - handler: fakeHandler - } - ]; - } - }); + initFields(meta.systemFields, component, unsafe); runHook('beforeCreate', component).catch(stderr); callMethodFromComponent(component, 'beforeCreate'); From 157c40deed80d950d03bc6ab7f2ec000760f82fc Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 30 Sep 2024 16:34:12 +0300 Subject: [PATCH 176/334] chore: refactoring --- src/components/super/i-block/modules/lfc/index.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/super/i-block/modules/lfc/index.ts b/src/components/super/i-block/modules/lfc/index.ts index ee125b136e..dd7f9f2898 100644 --- a/src/components/super/i-block/modules/lfc/index.ts +++ b/src/components/super/i-block/modules/lfc/index.ts @@ -16,11 +16,12 @@ import SyncPromise from 'core/promise/sync'; import type Async from 'core/async'; import type { AsyncOptions } from 'core/async'; +import { beforeHooks, Hook } from 'core/component'; + import Friend from 'components/friends/friend'; import { statuses } from 'components/super/i-block/const'; -import type { Hook } from 'core/component'; import type { Cb } from 'components/super/i-block/modules/lfc/interface'; export * from 'components/super/i-block/modules/lfc/interface'; @@ -44,14 +45,9 @@ export default class Lfc extends Friend { * ``` */ isBeforeCreate(...skip: Hook[]): boolean { - const beforeHooks = { - beforeRuntime: true, - beforeCreate: true, - beforeDataCreate: true - }; - - skip.forEach((hook) => beforeHooks[hook] = false); - return Boolean(beforeHooks[this.hook]); + const hooks = {...beforeHooks}; + skip.forEach((hook) => hooks[hook] = false); + return Boolean(hooks[this.hook]); } /** From e95d33cbaa8eceeee28cd3a58ebb977b88f80200 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 30 Sep 2024 17:54:54 +0300 Subject: [PATCH 177/334] fix: fixes after refactoring --- src/components/form/b-button/b-button.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/form/b-button/b-button.ts b/src/components/form/b-button/b-button.ts index 3b70a02c08..390eabd834 100644 --- a/src/components/form/b-button/b-button.ts +++ b/src/components/form/b-button/b-button.ts @@ -153,7 +153,12 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi } protected override syncDataProviderWatcher(initLoad: boolean = true): void { - if (this.href != null || this.dataProviderProp !== 'Provider') { + if ( + this.href != null || + this.request != null || + this.dataProviderProp !== 'Provider' || + this.dataProviderOptions != null + ) { super.syncDataProviderWatcher(initLoad); } } From 3d7bad018542348dd743a63378ebd0c783bfe06d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 30 Sep 2024 18:05:54 +0300 Subject: [PATCH 178/334] chore: optimization --- src/components/super/i-block/i-block.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/super/i-block/i-block.ts b/src/components/super/i-block/i-block.ts index 10ff183b28..b647be0dce 100644 --- a/src/components/super/i-block/i-block.ts +++ b/src/components/super/i-block/i-block.ts @@ -94,7 +94,8 @@ export default abstract class iBlock extends iBlockProviders { */ @watch({ path: 'r.shouldMountTeleports', - flush: 'post' + flush: 'post', + shouldInit: (o) => o.r.shouldMountTeleports === false }) @hook('before:mounted') From 4f5e4838ecdea189bf10977c79876215589a9b5b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 30 Sep 2024 18:06:12 +0300 Subject: [PATCH 179/334] chore: stylish fixes --- src/components/super/i-block/event/index.ts | 2 +- src/components/super/i-data/data.ts | 2 +- src/components/super/i-data/handlers.ts | 4 ++-- src/core/component/decorators/watch/README.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index 1b57990cd7..130d50729c 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -575,7 +575,7 @@ export default abstract class iBlockEvent extends iBlockBase { @watch({ path: 'proxyCall', immediate: true, - shouldInit: (ctx) => ctx.proxyCall != null + shouldInit: (o) => o.proxyCall != null }) protected initCallChildListener(enable: boolean): void { diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index 3a27bef06a..e27a4e2a8d 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -273,7 +273,7 @@ abstract class iDataData extends iBlock implements iDataProvider { */ @watch({ path: 'componentConverter', - shouldInit: (ctx) => ctx.componentConverter != null + shouldInit: (o) => o.componentConverter != null }) protected initRemoteData(): CanUndef { diff --git a/src/components/super/i-data/handlers.ts b/src/components/super/i-data/handlers.ts index c0a9a0d0bc..e541a72473 100644 --- a/src/components/super/i-data/handlers.ts +++ b/src/components/super/i-data/handlers.ts @@ -143,13 +143,13 @@ export default abstract class iDataHandlers extends iDataData { { path: 'dataProviderProp', provideArgs: false, - shouldInit: (ctx) => ctx.dataProviderProp != null + shouldInit: (o) => o.dataProviderProp != null }, { path: 'dataProviderOptions', provideArgs: false, - shouldInit: (ctx) => ctx.dataProviderOptions != null + shouldInit: (o) => o.dataProviderOptions != null } ]) diff --git a/src/core/component/decorators/watch/README.md b/src/core/component/decorators/watch/README.md index 9018993d87..ec7784653a 100644 --- a/src/core/component/decorators/watch/README.md +++ b/src/core/component/decorators/watch/README.md @@ -203,7 +203,7 @@ export default class bExample extends iBlock { @prop({required: false}) params?: Dictionary; - @watch({path: 'params', shouldInit: (ctx) => ctx.params != null}) + @watch({path: 'params', shouldInit: (o) => o.params != null}) watcher() { } From 399bbaa2454d4816568fc013cfa86deb2cafaf2a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 30 Sep 2024 19:17:08 +0300 Subject: [PATCH 180/334] chore: optimization --- src/core/component/event/component.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index 066c22ccfe..ab419329f9 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -84,11 +84,14 @@ export function implementEventEmitterAPI(component: object): void { writable: false, value(event: string, ...args: unknown[]) { - if (!event.startsWith('[[')) { - nativeEmit?.(event, ...args); + if (regularEmitter.hasListeners(event) === true) { + if (!event.startsWith('[[')) { + nativeEmit?.(event, ...args); + } + + regularEmitter.emit(event, ...args); } - regularEmitter.emit(event, ...args); return this; } }); From 07c8381ecd1c357a0e47696f5c8f4570483686f3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 1 Oct 2024 14:55:23 +0300 Subject: [PATCH 181/334] feat: added the new prop `getPassedHandlers` --- src/components/super/i-block/props.ts | 3 +++ .../interface/component/component.ts | 5 ++++ src/core/component/render/wrappers.ts | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/src/components/super/i-block/props.ts b/src/components/super/i-block/props.ts index 5671eff74f..c2f815d228 100644 --- a/src/components/super/i-block/props.ts +++ b/src/components/super/i-block/props.ts @@ -320,4 +320,7 @@ export default abstract class iBlockProps extends ComponentInterface { @prop({type: Function, required: false}) override readonly getPassedProps?: () => Set; + + @prop({type: Function, required: false}) + override readonly getPassedHandlers?: () => Set; } diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index 8e00ecb288..7f93239846 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -167,6 +167,11 @@ export abstract class ComponentInterface { */ abstract readonly getPassedProps?: () => Set; + /** + * The getter is used to get a set of event handlers that were passed to the component directly through the template + */ + abstract readonly getPassedHandlers?: () => Set; + /** * A string value indicating the lifecycle hook that the component is currently in. * For instance, `created`, `mounted` or `destroyed`. diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index a0cbf8fb7e..8539bbfe82 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -141,6 +141,29 @@ export function wrapCreateBlock(original: T): T { let passedProps: CanNull> = null; props.getPassedProps ??= () => passedProps ??= new Set(attrs != null ? Object.keys(attrs) : []); + let passedHandlers: CanNull> = null; + props.getPassedHandlers ??= () => { + if (passedHandlers != null) { + return passedHandlers; + } + + if (attrs == null) { + passedHandlers = new Set(); + + } else { + passedHandlers = new Set( + Object.keys(attrs) + .filter((prop) => prop.startsWith('on')) + .map((prop) => { + prop = prop.slice('on'.length); + return prop[0].toLowerCase() + prop.slice(1); + }) + ); + } + + return passedHandlers; + }; + // For refs within functional components, // it is necessary to explicitly set a reference to the instance of the component if (!SSR && vnode.ref != null && vnode.ref.i == null) { From ae8aa697845ded4434706486979fe408c218f24e Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 1 Oct 2024 14:56:55 +0300 Subject: [PATCH 182/334] fix: fixed the `emit` optimization --- src/core/component/event/component.ts | 31 ++++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index ab419329f9..8af67569bb 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -10,7 +10,7 @@ import type { EventId } from 'core/async'; import { EventEmitter2 as EventEmitter } from 'eventemitter2'; import * as gc from 'core/component/gc'; -import type { UnsafeComponentInterface, ComponentEmitterOptions } from 'core/component/interface'; +import type { ComponentInterface, UnsafeComponentInterface, ComponentEmitterOptions } from 'core/component/interface'; import { globalEmitter } from 'core/component/event/emitter'; import type { ComponentResetType } from 'core/component/event/interface'; @@ -59,8 +59,9 @@ export function resetComponents(type?: ComponentResetType): void { */ export function implementEventEmitterAPI(component: object): void { const - ctx = Object.cast(component), - nativeEmit = Object.cast>(ctx.$emit); + ctx = Object.cast(component), + unsafe = Object.cast(component), + nativeEmit = Object.cast>(unsafe.$emit); const regularEmitter = new EventEmitter({ maxListeners: 1e3, @@ -68,7 +69,7 @@ export function implementEventEmitterAPI(component: object): void { wildcard: true }); - const wrappedEmitter = ctx.$async.wrapEventEmitter(regularEmitter); + const wrappedEmitter = unsafe.$async.wrapEventEmitter(regularEmitter); const reversedEmitter = Object.cast({ on: (...args: Parameters) => regularEmitter.prependListener(...args), @@ -78,49 +79,49 @@ export function implementEventEmitterAPI(component: object): void { const wrappedReversedEmitter = Object.cast(reversedEmitter); - Object.defineProperty(ctx, '$emit', { + Object.defineProperty(unsafe, '$emit', { configurable: true, enumerable: false, writable: false, value(event: string, ...args: unknown[]) { - if (regularEmitter.hasListeners(event) === true) { - if (!event.startsWith('[[')) { - nativeEmit?.(event, ...args); - } + const needNativeEvent = nativeEmit != null && !event.startsWith('[[') && ctx.getPassedHandlers?.().has(event); - regularEmitter.emit(event, ...args); + if (needNativeEvent) { + nativeEmit(event, ...args); } + regularEmitter.emit(event, ...args); + return this; } }); - Object.defineProperty(ctx, '$on', { + Object.defineProperty(unsafe, '$on', { configurable: true, enumerable: false, writable: false, value: getMethod('on') }); - Object.defineProperty(ctx, '$once', { + Object.defineProperty(unsafe, '$once', { configurable: true, enumerable: false, writable: false, value: getMethod('once') }); - Object.defineProperty(ctx, '$off', { + Object.defineProperty(unsafe, '$off', { configurable: true, enumerable: false, writable: false, value: getMethod('off') }); - ctx.$destructors.push(() => { + unsafe.$destructors.push(() => { gc.add(function* destructor() { for (const key of ['$emit', '$on', '$once', '$off']) { - Object.defineProperty(ctx, key, { + Object.defineProperty(unsafe, key, { configurable: true, enumerable: true, writable: false, From fd62143864a3fe53d395294173f294b4d3a75e37 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 2 Oct 2024 15:57:04 +0300 Subject: [PATCH 183/334] chore: optimize event API --- src/components/super/i-block/event/index.ts | 148 ++++++++++++------ .../super/i-block/event/interface.ts | 141 +++++++++++------ .../super/i-block/modules/mods/index.ts | 4 +- .../super/i-block/providers/index.ts | 13 +- src/components/super/i-block/state/index.ts | 27 +++- src/components/super/i-data/data.ts | 4 +- src/components/super/i-data/i-data.ts | 2 +- src/components/traits/i-open/i-open.ts | 10 +- src/components/traits/i-visible/i-visible.ts | 4 +- 9 files changed, 232 insertions(+), 121 deletions(-) diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index 130d50729c..fdec2b9f98 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -21,6 +21,8 @@ import type { AsyncOptions, EventEmitterWrapper, ReadonlyEventEmitterWrapper, Ev import { component, globalEmitter, ComponentEmitterOptions } from 'core/component'; +import type { SetModEvent, ModEvent } from 'components/friends/block'; + import { computed, hook, watch } from 'components/super/i-block/decorators'; import { initGlobalListeners } from 'components/super/i-block/modules/listeners'; @@ -43,7 +45,10 @@ export default abstract class iBlockEvent extends iBlockBase { * Events are described using tuples, where the first element is the event name, and the rest are arguments. */ readonly SelfEmitter!: InferComponentEvents; /** @@ -103,7 +108,7 @@ export default abstract class iBlockEvent extends iBlockBase { }, emit: this.emit.bind(this), - strictEmit: this.emit.bind(this) + strictEmit: this.strictEmit.bind(this) }); return Object.cast(emitter); @@ -333,8 +338,47 @@ export default abstract class iBlockEvent extends iBlockBase { */ emit: typeof this['selfEmitter']['emit'] = function emit(this: iBlockEvent, event: string | ComponentEvent, ...args: unknown[]): void { + // @ts-ignore (cast) + this.strictEmit(normalizeEvent(event), ...args); + }; + + /** + * Emits a component event. + * + * All events fired by this method can be listened to "outside" using the `v-on` directive. + * Also, if the component is in `dispatching` mode, then this event will start bubbling up to the parent component. + * + * In addition, all emitted events are automatically logged using the `log` method. + * The default logging level is `info` (logging requires the `verbose` prop to be set to true), + * but you can set the logging level explicitly. + * + * Note that this method always fires three events: + * + * 1. `${event}`(self, ...args) - the first argument is passed as a link to the component that emitted the event. + * 2. `${event}:component`(self, ...args) - the event to avoid collisions between component events and + * native DOM events. + * + * 3. `on-${event}`(...args) + * + * @param event - the event name to dispatch + * @param args - the event arguments + * + * @example + * ```js + * this.on('someEvent', console.log); // [this, 42] + * this.on('onSomeEvent', console.log); // [42] + * + * this.emit('someEvent', 42); + * + * // Enable logging + * setEnv('log', {patterns: ['event:']}); + * this.emit({event: 'someEvent', logLevel: 'warn'}, 42); + * ``` + */ + strictEmit: typeof this['selfEmitter']['strictEmit'] = + function strictEmit(this: iBlockEvent, event: string | ComponentEvent, ...args: any[]): void { const - eventDecl = normalizeEvent(event), + eventDecl = normalizeStrictEvent(event), eventName = eventDecl.event; this.$emit(eventName, this, ...args); @@ -342,32 +386,22 @@ export default abstract class iBlockEvent extends iBlockBase { this.$emit(getWrappedEventName(eventName), ...args); if (this.dispatching) { - this.dispatch(eventDecl, ...args); + this.dispatch(event, ...args); } - const logArgs = args.slice(); + if (!Object.isString(event)) { + const logArgs = args.slice(); - if (eventDecl.logLevel === 'error') { - logArgs.forEach((el, i) => { - if (Object.isFunction(el)) { - logArgs[i] = () => el; - } - }); - } - - this.log({context: `event:${eventName}`, logLevel: eventDecl.logLevel}, this, ...logArgs); - }; + if (eventDecl.logLevel === 'error') { + logArgs.forEach((el, i) => { + if (Object.isFunction(el)) { + logArgs[i] = () => el; + } + }); + } - /** - * An alias for the `emit` method, but with stricter type checking - * - * @alias - * @param event - * @param args - */ - strictEmit: typeof this['selfEmitter']['strictEmit'] = - function strictEmit(this: iBlockEvent, event: string | ComponentEvent, ...args: any[]): void { - this.emit(event, ...args); + this.log({context: `event:${eventName}`, logLevel: eventDecl.logLevel}, this, ...logArgs); + } }; /** @@ -426,26 +460,26 @@ export default abstract class iBlockEvent extends iBlockBase { function dispatch(this: iBlockEvent, event: string | ComponentEvent, ...args: unknown[]): void { const that = this; - const eventDecl = normalizeEvent(event); + const eventDecl = normalizeStrictEvent(event); const eventName = eventDecl.event, wrappedEventName = getWrappedEventName(eventName); - let { - globalName, - componentName, - $parent: parent - } = this; + let {globalName, componentName, $parent: parent} = this; - const logArgs = args.slice(); - - if (eventDecl.logLevel === 'error') { - logArgs.forEach((el, i) => { - if (Object.isFunction(el)) { - logArgs[i] = () => el; - } - }); + const + log = Object.isString(event), + logArgs = log ? args.slice() : args; + + if (log) { + if (eventDecl.logLevel === 'error') { + logArgs.forEach((el, i) => { + if (Object.isFunction(el)) { + logArgs[i] = () => el; + } + }); + } } while (parent != null) { @@ -453,16 +487,27 @@ export default abstract class iBlockEvent extends iBlockBase { parent.$emit(eventName, this, ...args); parent.$emit(getComponentEventName(eventName), this, ...args); parent.$emit(wrappedEventName, ...args); - logFromParent(parent, `event:${eventName}`); + + if (log) { + logFromParent(parent, `event:${eventName}`); + } } else { parent.$emit(normalizeEventName(`${componentName}::${eventName}`), this, ...args); parent.$emit(normalizeEventName(`${componentName}::${wrappedEventName}`), ...args); - logFromParent(parent, `event:${componentName}::${eventName}`); + + if (log) { + logFromParent(parent, `event:${componentName}::${eventName}`); + } if (globalName != null) { parent.$emit(normalizeEventName(`${globalName}::${eventName}`), this, ...args); parent.$emit(normalizeEventName(`${globalName}::${wrappedEventName}`), ...args); + + if (log) { + logFromParent(parent, `event:${componentName}::${eventName}`); + } + logFromParent(parent, `event:${globalName}::${eventName}`); } } @@ -600,26 +645,31 @@ export default abstract class iBlockEvent extends iBlockBase { } } -function normalizeEvent(event: ComponentEvent | string): ComponentEvent { +function normalizeEvent(event: ComponentEvent | string): string | ComponentEvent { if (Object.isString(event)) { - return { - event: normalizeEventName(event), - logLevel: 'info' - }; + return normalizeEventName(event); } return { - ...event, + logLevel: event.logLevel, event: normalizeEventName(event.event) }; } +function normalizeStrictEvent(event: ComponentEvent | string): ComponentEvent { + if (Object.isString(event)) { + return {event, logLevel: 'info'}; + } + + return event; +} + function getWrappedEventName(event: string): string { - return normalizeEventName(`on-${event}`); + return `on${event[0].toUpperCase()}${event.slice(1)}`; } function getComponentEventName(event: string): string { - return normalizeEventName(`${event}:component`); + return `${event}:component`; } function normalizeEventName(event: string): string { diff --git a/src/components/super/i-block/event/interface.ts b/src/components/super/i-block/event/interface.ts index 2c398cac0f..a45ea878c9 100644 --- a/src/components/super/i-block/event/interface.ts +++ b/src/components/super/i-block/event/interface.ts @@ -9,78 +9,117 @@ import type { LogLevel } from 'core/log'; export type InferEvents< - I extends Array<[string, ...any[]]>, - P extends Dictionary = {}, - R extends Dictionary = {} + Scheme extends Array<[string, ...any[]]>, + Parent extends Dictionary = {}, + Result extends Dictionary = {} > = { - 0: InferEvents, P, (TB.Head extends [infer E, ...infer A] ? - E extends string ? { - Args: {[K in E]: A}; + 0: InferEvents, Parent, (TB.Head extends [infer E, ...infer A] ? E extends string ? { + on(event: E, cb: (...args: A) => void): void; + once(event: E, cb: (...args: A) => void): void; + promisifyOnce(event: E): Promise>>; - on(event: E, cb: (...args: A) => void): void; - once(event: E, cb: (...args: A) => void): void; - promisifyOnce(event: E): Promise>>; + off(event: E | string, handler?: Function): void; - off(event: E | string, handler?: Function): void; + strictEmit(event: E, ...args: A): void; + emit(event: E, ...args: A): void; + emit(event: string, ...args: unknown[]): void; + } : {} : {}) & Result>; - strictEmit(event: E, ...args: A): void; - emit(event: E, ...args: A): void; - emit(event: string, ...args: unknown[]): void; - } : {} : {}) & R>; - - 1: R & P; -}[TB.Length extends 0 ? 1 : 0]; + 1: Result & Parent; +}[TB.Length extends 0 ? 1 : 0]; export interface ComponentEvent { event: E; logLevel?: LogLevel; } +type UnionToIntersection = (T extends any ? (k: T) => void : never) extends (k: infer R) => void ? R : never; + +type EventToTuple = + UnionToIntersection Event : never> extends (() => infer T) ? + [...EventToTuple, Result>, [T, ...Result]] : + []; + +export type FlatEvents = { + 0: TB.Head extends [infer E, ...infer A] ? + FlatEvents, [...EventToTuple, ...Result]> : + []; + + 1: Result; +}[TB.Length extends 0 ? 1 : 0]; + export type InferComponentEvents< - C, - I extends Array<[string, ...any[]]>, - P extends Dictionary = {}, - R extends Dictionary = {} + Ctx, + Scheme extends Array<[string, ...any[]]>, + Parent extends Dictionary = {} +> = _InferComponentEvents, Parent>; + +export type _InferComponentEvents< + Ctx, + Scheme extends any[], + Parent extends Dictionary = {}, + Events extends any[] = [], + Result extends Dictionary = {} > = { - 0: InferComponentEvents, P, (TB.Head extends [infer E, ...infer A] ? E extends string ? { - Args: {[K in E]: A}; + 0: _InferComponentEvents< + Ctx, + + TB.Tail, - on(event: `on${Capitalize}`, cb: (...args: A) => void): void; - on(event: E | `${E}:component`, cb: (component: C, ...args: A) => void): void; + Parent, - once(event: `on${Capitalize}`, cb: (...args: A) => void): void; - once(event: E | `${E}:component`, cb: (component: C, ...args: A) => void): void; + TB.Head extends [infer E, ...infer A] ? [...Events, [E, ...A]] : Events, - promisifyOnce(event: `on${Capitalize}`): Promise>>; - promisifyOnce(event: E | `${E}:component`): Promise>; + (TB.Head extends [infer E, ...infer A] ? E extends string ? { + on(event: `on${Capitalize}`, cb: (...args: A) => void): void; + on(event: E | `${E}:component`, cb: (component: Ctx, ...args: A) => void): void; - off(event: E | `${E}:component` | `on${Capitalize}` | string, handler?: Function): void; + once(event: `on${Capitalize}`, cb: (...args: A) => void): void; + once(event: E | `${E}:component`, cb: (component: Ctx, ...args: A) => void): void; - strictEmit(event: E | ComponentEvent, ...args: A): void; - emit(event: E | ComponentEvent, ...args: A): void; - emit(event: string | ComponentEvent, ...args: unknown[]): void; - } : {} : {}) & R>; + promisifyOnce(event: `on${Capitalize}`): Promise>>; + promisifyOnce(event: E | `${E}:component`): Promise>; - 1: R & OverrideParentComponentEvents; -}[TB.Length extends 0 ? 1 : 0]; + off(event: E | `${E}:component` | `on${Capitalize}` | string, handler?: Function): void; -export type OverrideParentComponentEvents = A extends Record ? { - [E in keyof A]: E extends string ? { - Args: A; + strictEmit(event: E | ComponentEvent, ...args: A): void; + emit(event: E | ComponentEvent, ...args: A): void; + emit(event: string | ComponentEvent, ...args: unknown[]): void; + } : {} : {}) & Result>; + + 1: Parent extends {Events: infer ParentEvents} ? ParentEvents extends any[] ? + Overwrite< + Result & OverrideParentComponentEvents, + {Events: [...ParentEvents, ...Events]} + > : + + Result & {Events: Events} : Result & {Events: Events}; + +}[TB.Length extends 0 ? 1 : 0]; + +export type OverrideParentComponentEvents< + Ctx, + Parent extends Dictionary, + Events extends any[], + Result extends Dictionary = {} +> = { + 0: TB.Head extends [infer E, ...infer A] ? E extends string ? + OverrideParentComponentEvents, Result & { + on(event: `on${Capitalize}`, cb: (...args: A) => void): void; + on(event: E | `${E}:component`, cb: (component: Ctx, ...args: A) => void): void; - on(event: `on${Capitalize}`, cb: (...args: A[E]) => void): void; - on(event: E | `${E}:component`, cb: (component: C, ...args: A[E]) => void): void; + once(event: `on${Capitalize}`, cb: (...args: A) => void): void; + once(event: E | `${E}:component`, cb: (component: Ctx, ...args: A) => void): void; - once(event: `on${Capitalize}`, cb: (...args: A[E]) => void): void; - once(event: E | `${E}:component`, cb: (component: C, ...args: A[E]) => void): void; + promisifyOnce(event: `on${Capitalize}`): Promise>>; + promisifyOnce(event: E | `${E}:component`): Promise>; - promisifyOnce(event: `on${Capitalize}`): Promise>>; - promisifyOnce(event: E | `${E}:component`): Promise>; + off(event: E | `${E}:component` | `on${Capitalize}` | string, handler?: Function): void; - off(event: E | `${E}:component` | `on${Capitalize}` | string, handler?: Function): void; + strictEmit(event: E | ComponentEvent, ...args: A): void; + emit(event: E | ComponentEvent, ...args: A): void; + emit(event: string | ComponentEvent, ...args: unknown[]): void; + }> : Result : Result; - strictEmit(event: E | ComponentEvent, ...args: A[E]): void; - emit(event: E | ComponentEvent, ...args: A[E]): void; - emit(event: string | ComponentEvent, ...args: unknown[]): void; - } : {}; -}[keyof A] : {}; + 1: Result; +}[TB.Length extends 0 ? 1 : 0]; diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index cf8324053f..63bd94e5d7 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -179,8 +179,10 @@ export function mergeMods( declMods = component.meta.component.mods, res = {...modsProp}; - Object.entries(component.$attrs).forEach(([name, attr]) => { + component.getPassedProps?.().forEach((name) => { if (name in declMods) { + const attr = component.$attrs[name]; + if (attr != null) { res[name] = attr; } diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 7575fe297b..66d1952304 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -32,6 +32,8 @@ import { system, hook } from 'components/super/i-block/decorators'; import type { InitLoadCb, InitLoadOptions } from 'components/super/i-block/interface'; import iBlockState from 'components/super/i-block/state'; + +import type { InferComponentEvents } from 'components/super/i-block/event'; import type { DataProviderProp } from 'components/super/i-block/providers/interface'; export * from 'components/super/i-block/providers/interface'; @@ -41,6 +43,13 @@ const @component({partial: 'iBlock'}) export default abstract class iBlockProviders extends iBlockState { + /** @inheritDoc */ + // @ts-ignore (override) + declare readonly SelfEmitter: InferComponentEvents; + /** {@link iBlock.dontWaitRemoteProvidersProp} */ @system((o) => o.sync.link((val) => { if (val == null) { @@ -109,7 +118,7 @@ export default abstract class iBlockProviders extends iBlockState { try { if (opts.emitStartEvent !== false) { - this.emit('initLoadStart', opts); + this.strictEmit('initLoadStart', opts); } if (!opts.silent) { @@ -228,7 +237,7 @@ export default abstract class iBlockProviders extends iBlockState { } function emitInitLoad() { - that.emit('initLoad', get(), opts); + that.strictEmit('initLoad', get(), opts); } } diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 43ff23c4b9..f9ad390d3d 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -30,12 +30,27 @@ import { readyStatuses } from 'components/super/i-block/modules/activation'; import { field, system, computed, wait, hook, WaitDecoratorOptions } from 'components/super/i-block/decorators'; import type iBlock from 'components/super/i-block/i-block'; + +import type { InferComponentEvents } from 'components/super/i-block/event'; import type { Stage, ComponentStatus, ComponentStatuses } from 'components/super/i-block/interface'; import iBlockMods from 'components/super/i-block/mods'; @component({partial: 'iBlock'}) export default abstract class iBlockState extends iBlockMods { + /** @inheritDoc */ + // @ts-ignore (override) + declare readonly SelfEmitter: InferComponentEvents, CanUndef], + ['stageChange', CanUndef, CanUndef] + ], iBlockMods['SelfEmitter']>; + /** * A list of additional dependencies to load during the component's initialization * {@link iBlock.dependenciesProp} @@ -217,8 +232,8 @@ export default abstract class iBlockState extends iBlockMods { } } - this.emit(`componentStatus:${value}`, value, oldValue); - this.emit('componentStatusChange', value, oldValue); + this.strictEmit(`componentStatus:${value}`, value, oldValue); + this.strictEmit('componentStatusChange', value, oldValue); } // eslint-disable-next-line jsdoc/require-param @@ -290,10 +305,10 @@ export default abstract class iBlockState extends iBlockMods { } if (value != null) { - this.emit(`stage:${value}`, value, oldValue); + this.strictEmit(`stage:${value}`, value, oldValue); } - this.emit('stageChange', value, oldValue); + this.strictEmit('stageChange', value, oldValue); } /** @@ -377,8 +392,8 @@ export default abstract class iBlockState extends iBlockMods { this.hookStore = value; if ('lfc' in this && !this.lfc.isBeforeCreate('beforeDataCreate')) { - this.emit(`hook:${value}`, value, oldValue); - this.emit('hookChange', value, oldValue); + this.strictEmit(`hook:${value}`, value, oldValue); + this.strictEmit('hookChange', value, oldValue); } } diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index e27a4e2a8d..f0cd18ed26 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -173,7 +173,7 @@ abstract class iDataData extends iBlock implements iDataProvider { * @emits `dbChange(value: CanUndef)` */ set db(value: CanUndef) { - this.emit('dbCanChange', value); + this.strictEmit('dbCanChange', value); if (value === this.db) { return; @@ -194,7 +194,7 @@ abstract class iDataData extends iBlock implements iDataProvider { }); } - this.emit('dbChange', value); + this.strictEmit('dbChange', value); } static override readonly mods: ModsDecl = { diff --git a/src/components/super/i-data/i-data.ts b/src/components/super/i-data/i-data.ts index 0b23883f83..8f95b5bc9d 100644 --- a/src/components/super/i-data/i-data.ts +++ b/src/components/super/i-data/i-data.ts @@ -84,7 +84,7 @@ export default abstract class iData extends iDataHandlers { try { if (opts.emitStartEvent !== false) { - this.emit('initLoadStart', opts); + this.strictEmit('initLoadStart', opts); } if (this.dataProviderProp != null && this.dataProvider == null) { diff --git a/src/components/traits/i-open/i-open.ts b/src/components/traits/i-open/i-open.ts index bed33aba85..89cbdbc70a 100644 --- a/src/components/traits/i-open/i-open.ts +++ b/src/components/traits/i-open/i-open.ts @@ -53,8 +53,7 @@ export default abstract class iOpen { /** {@link iOpen.prototype.onTouchClose} */ static onTouchClose: AddSelf = async (component, e) => { - const - target = >e.target; + const target = >e.target; if (target == null) { return; @@ -80,10 +79,7 @@ export default abstract class iOpen { events: CloseHelperEvents = {}, eventOpts: AddEventListenerOptions = {} ): void { - const { - async: $a, - localEmitter: $e - } = component.unsafe; + const {async: $a, localEmitter: $e} = component.unsafe; const helpersGroup = {group: 'closeHelpers'}, @@ -144,7 +140,7 @@ export default abstract class iOpen { return; } - component.emit(e.value === 'false' || e.type === 'remove' ? 'close' : 'open'); + component.strictEmit(e.value === 'false' || e.type === 'remove' ? 'close' : 'open'); }); } diff --git a/src/components/traits/i-visible/i-visible.ts b/src/components/traits/i-visible/i-visible.ts index 67ed586a7e..ce43d91789 100644 --- a/src/components/traits/i-visible/i-visible.ts +++ b/src/components/traits/i-visible/i-visible.ts @@ -51,11 +51,11 @@ export default abstract class iVisible { if (e.value === 'false' || e.type === 'remove') { $el?.setAttribute('aria-hidden', 'true'); - component.emit('show'); + component.strictEmit('show'); } else { $el?.setAttribute('aria-hidden', 'false'); - component.emit('hide'); + component.strictEmit('hide'); } }); } From aebabb51f1294edc77ca5e0eb489b8c43af644b4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 2 Oct 2024 15:58:23 +0300 Subject: [PATCH 184/334] chore: optimization --- .../b-remote-provider/b-remote-provider.ts | 20 ++++++++----------- src/components/base/b-tree/b-tree.ts | 7 ++----- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/components/base/b-remote-provider/b-remote-provider.ts b/src/components/base/b-remote-provider/b-remote-provider.ts index 60fcabb97b..57d37af6a4 100644 --- a/src/components/base/b-remote-provider/b-remote-provider.ts +++ b/src/components/base/b-remote-provider/b-remote-provider.ts @@ -100,10 +100,9 @@ export default class bRemoteProvider extends iData { * @emits `error(err:Error |` [[RequestError]]`, retry:` [[RetryRequestFn]]`)` */ protected override onRequestError(err: Error | RequestError, retry: RetryRequestFn): void { - const - a = this.$attrs; + const a = this.getPassedHandlers?.(); - if (a.onError == null && a.onOnError == null) { + if (a != null && (a.has('error') || a.has('error:component') || a.has('onError'))) { super.onRequestError(err, retry); } @@ -117,10 +116,9 @@ export default class bRemoteProvider extends iData { * @emits `addData(data: unknown)` */ protected override onAddData(data: unknown): void { - const - a = this.$attrs; + const a = this.getPassedHandlers?.(); - if (a.onAddData == null && a.onOnAddData == null) { + if (a != null && (a.has('addData') || a.has('addData:component') || a.has('onAddData'))) { return super.onAddData(data); } @@ -134,10 +132,9 @@ export default class bRemoteProvider extends iData { * @emits `updateData(data: unknown)` */ protected override onUpdateData(data: unknown): void { - const - a = this.$attrs; + const a = this.getPassedHandlers?.(); - if (a.onUpdateData == null && a.onOnUpdateData == null) { + if (a != null && (a.has('updateData') || a.has('updateData:component') || a.has('onUpdateData'))) { return super.onUpdateData(data); } @@ -151,10 +148,9 @@ export default class bRemoteProvider extends iData { * @emits `deleteData(data: unknown)` */ protected override onDeleteData(data: unknown): void { - const - a = this.$attrs; + const a = this.getPassedHandlers?.(); - if (a.onDeleteData == null && a.onOnDeleteData == null) { + if (a != null && (a.has('deleteData') || a.has('deleteData:component') || a.has('onDeleteData'))) { return super.onDeleteData(data); } diff --git a/src/components/base/b-tree/b-tree.ts b/src/components/base/b-tree/b-tree.ts index 806ae48c84..63d602cf27 100644 --- a/src/components/base/b-tree/b-tree.ts +++ b/src/components/base/b-tree/b-tree.ts @@ -185,11 +185,8 @@ class bTree extends iTreeProps implements iActiveItems, iFoldable { renderFilter }; - const - a = this.$attrs; - - if (a.onFold != null) { - opts['@fold'] = a.onFold; + if (this.getPassedHandlers?.().has('fold')) { + opts['@fold'] = this.$attrs.onFold; } return opts; From f225aa268a97678fe172d5e9f4abaaf0cb4fb109 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 2 Oct 2024 15:58:39 +0300 Subject: [PATCH 185/334] chore: optimization --- src/components/friends/block/block.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/friends/block/block.ts b/src/components/friends/block/block.ts index 8d66531e5f..f95773d350 100644 --- a/src/components/friends/block/block.ts +++ b/src/components/friends/block/block.ts @@ -171,8 +171,8 @@ export function setMod(this: Block, name: string, value: unknown, reason: ModEve this.localEmitter.emit(`block.mod.set.${name}.${normalizedValue}`, event); if (!isInit) { - ctx.emit(`mod:set:${name}`, event); - ctx.emit(`mod:set:${name}:${normalizedValue}`, event); + ctx.strictEmit(`mod:set:${name}`, event); + ctx.strictEmit(`mod:set:${name}:${normalizedValue}`, event); } return true; @@ -251,7 +251,7 @@ export function removeMod(this: Block, name: string, value?: unknown, reason: Mo this.localEmitter.emit(`block.mod.remove.${name}.${currentValue}`, event); if (needNotify) { - ctx.emit(`mod:remove:${name}`, event); + ctx.strictEmit(`mod:remove:${name}`, event); } } From 3ff0c192c729ec9fa2da32133d84624c95def2e6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 2 Oct 2024 15:59:12 +0300 Subject: [PATCH 186/334] chore: optimization --- src/core/component/prop/helpers.ts | 4 ++-- src/core/component/prop/init.ts | 13 ++++++------- src/core/component/watch/create.ts | 10 +++++----- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index 07b0da05e5..deb692c86a 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -36,13 +36,13 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { el = unsafe.$el, propValuesToUpdate: string[][] = []; - Object.entries(unsafe.$attrs).forEach(([attrName, attrVal]) => { + unsafe.getPassedProps?.()?.forEach((attrName) => { const propPrefix = 'on:'; if (meta.props[attrName]?.forceUpdate === false) { const getterName = propPrefix + attrName; - if (attrVal !== undefined && !Object.isFunction(unsafe.$attrs[getterName])) { + if (!Object.isFunction(unsafe.$attrs[getterName])) { throw new Error(`No accessors are defined for the prop "${attrName}". To set the accessors, pass them as ":${attrName} = propValue | @:${attrName} = createPropAccessors(() => propValue)()" or "v-attrs = {'@:${attrName}': createPropAccessors(() => propValue)}".`); } } diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index 2ba8478076..561d2207a5 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -38,10 +38,7 @@ export function initProps( ...opts }; - const { - store, - from - } = p; + const {store, from} = p; const isFunctional = meta.params.functional === true, @@ -60,10 +57,12 @@ export function initProps( let propValue = (from ?? component)[propName]; - const getAccessors = unsafe.$attrs[`on:${propName}`]; + if (propValue === undefined && unsafe.getPassedHandlers?.().has(`:${propName}`)) { + const getAccessors = unsafe.$attrs[`on:${propName}`]; - if (propValue === undefined && Object.isFunction(getAccessors)) { - propValue = getAccessors()[0]; + if (Object.isFunction(getAccessors)) { + propValue = getAccessors()[0]; + } } let needSaveToStore = opts.saveToStore; diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 33b9ce3189..4d62160a00 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -347,13 +347,13 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface destructors: Function[] = []; const attachDeepProxy = (forceUpdate = true) => { - const getAccessors: CanUndef> = Object.cast( - this.$attrs[`on:${prop}`] - ); - - let accessors: Nullable>>; + let accessors: Nullable>>; if (!forceUpdate) { + const getAccessors: CanUndef> = Object.cast( + this.$attrs[`on:${prop}`] + ); + accessors = getAccessors?.(); } From 6d189219f75ff4ab407397ba981886ecfe545c2f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 2 Oct 2024 17:49:51 +0300 Subject: [PATCH 187/334] chore: updated tests --- .../test/unit/functional/emitter/payload.ts | 16 ++++++++-------- .../test/unit/functional/props/props.ts | 2 +- .../test/unit/functional/state/emitter.ts | 10 +++++----- .../test/unit/scenario/last-render.ts | 2 +- .../test/unit/scenario/reload.ts | 8 ++++---- tests/helpers/component-object/README.md | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/emitter/payload.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/emitter/payload.ts index 15cb727061..1be4a198d9 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/emitter/payload.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/emitter/payload.ts @@ -41,14 +41,14 @@ test.describe('', () => { .withProps({ chunkSize, shouldStopRequestingData: () => true, - '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') + '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'strictEmit') }) .build(); await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), calls = filterEmitterCalls(await spy.calls); test.expect(calls).toEqual([ @@ -86,7 +86,7 @@ test.describe('', () => { .withProps({ chunkSize, shouldStopRequestingData: ({lastLoadedData}) => lastLoadedData.length === 0, - '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') + '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'strictEmit') }) .build(); @@ -95,7 +95,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), calls = filterEmitterCalls(await spy.calls); test.expect(calls).toEqual([ @@ -139,7 +139,7 @@ test.describe('', () => { .withProps({ chunkSize, shouldStopRequestingData: ({lastLoadedData}) => lastLoadedData.length === 0, - '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') + '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'strictEmit') }) .build(); @@ -147,7 +147,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), calls = filterEmitterCalls(await spy.calls); test.expect(calls).toEqual([ @@ -181,7 +181,7 @@ test.describe('', () => { .withProps({ chunkSize, shouldStopRequestingData: () => true, - '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') + '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'strictEmit') }) .build(); @@ -195,7 +195,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), calls = filterEmitterCalls(await spy.calls); test.expect(calls).toEqual([ diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts index 42e6ae3fd0..f473fdac40 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts @@ -305,7 +305,7 @@ test.describe('', () => { const virtualScrolLState = await component.getVirtualScrollState(), - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), loadSuccessCalls = (await spy.results).filter(({value: [event]}) => event === 'dataLoadSuccess'); test.expect(loadSuccessCalls).toHaveLength(1); diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts index 788b33ccae..b1c1e8e231 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts @@ -92,7 +92,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), results = filterEmitterResults(await spy.results, true, ['initLoadStart', 'initLoad']); test.expect(results).toEqual([ @@ -184,7 +184,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), results = filterEmitterResults(await spy.results, true, ['initLoadStart', 'initLoad']); test.expect(results).toEqual([ @@ -312,7 +312,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), results = filterEmitterResults(await spy.results, true, ['initLoadStart', 'initLoad']); test.expect(results).toEqual([ @@ -399,7 +399,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), results = filterEmitterResults(await spy.results, true, ['initLoadStart', 'initLoad']); test.expect(results).toEqual([ @@ -503,7 +503,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), results = filterEmitterResults(await spy.results, true, ['initLoadStart', 'initLoad']); test.expect(results).toEqual([ diff --git a/src/components/base/b-virtual-scroll-new/test/unit/scenario/last-render.ts b/src/components/base/b-virtual-scroll-new/test/unit/scenario/last-render.ts index 0267962350..8e58b883c9 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/scenario/last-render.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/scenario/last-render.ts @@ -116,7 +116,7 @@ test.describe('', () => { await component.waitForLifecycleDone(); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), results = filterEmitterResults(await spy.results, true, ['initLoadStart', 'initLoad']); test.expect(results).toEqual([ diff --git a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts index 89ee723ba7..574393d586 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/scenario/reload.ts @@ -42,7 +42,7 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize: chunkSize[0]}) .withProps({ chunkSize: chunkSize[0], - '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') + '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'strictEmit') }) .build({useDummy: true}); @@ -62,7 +62,7 @@ test.describe('', () => { await component.waitForDataIndexChild(chunkSize[1] - 1); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), calls = filterEmitterCalls(await spy.calls, true, ['initLoadStart', 'initLoad']).map(([event]) => event); test.expect(calls).toEqual([ @@ -110,7 +110,7 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize}) .withProps({ chunkSize, - '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') + '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'strictEmit') }) .build(); @@ -119,7 +119,7 @@ test.describe('', () => { await component.waitForDataIndexChild(chunkSize * 2 - 1); const - spy = await component.getSpy((ctx) => ctx.emit), + spy = await component.getSpy((ctx) => ctx.strictEmit), calls = filterEmitterCalls(await spy.calls, true, ['initLoadStart', 'initLoad']).map(([event]) => event); test.expect(calls).toEqual([ diff --git a/tests/helpers/component-object/README.md b/tests/helpers/component-object/README.md index cce58fc90e..e6eb732f97 100644 --- a/tests/helpers/component-object/README.md +++ b/tests/helpers/component-object/README.md @@ -274,11 +274,11 @@ Here's an example of setting up a spy to track the `emit` method of a component const myComponent = new MyComponentObject(page, 'b-component'); await myComponent.withProps({ - '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit'), + '@hook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'strictEmit'), }); // Extract the spy -const spy = await myComponent.component.getSpy((ctx) => ctx.emit); +const spy = await myComponent.component.getSpy((ctx) => ctx.strictEmit); // Access the spy console.log(await spy.calls); From b28f6e878970d71ebedd66a4f2e74fba963337d4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 14:52:24 +0300 Subject: [PATCH 188/334] fix: fixes after refactoring --- src/components/base/b-remote-provider/b-remote-provider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/base/b-remote-provider/b-remote-provider.ts b/src/components/base/b-remote-provider/b-remote-provider.ts index 57d37af6a4..32fca0bf9c 100644 --- a/src/components/base/b-remote-provider/b-remote-provider.ts +++ b/src/components/base/b-remote-provider/b-remote-provider.ts @@ -118,7 +118,7 @@ export default class bRemoteProvider extends iData { protected override onAddData(data: unknown): void { const a = this.getPassedHandlers?.(); - if (a != null && (a.has('addData') || a.has('addData:component') || a.has('onAddData'))) { + if (a == null || !a.has('addData') && !a.has('addData:component') && !a.has('onAddData')) { return super.onAddData(data); } @@ -134,7 +134,7 @@ export default class bRemoteProvider extends iData { protected override onUpdateData(data: unknown): void { const a = this.getPassedHandlers?.(); - if (a != null && (a.has('updateData') || a.has('updateData:component') || a.has('onUpdateData'))) { + if (a != null && !a.has('updateData') && !a.has('updateData:component') && !a.has('onUpdateData')) { return super.onUpdateData(data); } @@ -150,7 +150,7 @@ export default class bRemoteProvider extends iData { protected override onDeleteData(data: unknown): void { const a = this.getPassedHandlers?.(); - if (a != null && (a.has('deleteData') || a.has('deleteData:component') || a.has('onDeleteData'))) { + if (a == null || !a.has('deleteData') && !a.has('deleteData:component') && !a.has('onDeleteData')) { return super.onDeleteData(data); } From 53a9c554fe43382d8c9219bd3e58dbeb88d104f5 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 14:54:35 +0300 Subject: [PATCH 189/334] fix: fixes after refactoring --- .../base/b-remote-provider/b-remote-provider.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/base/b-remote-provider/b-remote-provider.ts b/src/components/base/b-remote-provider/b-remote-provider.ts index 32fca0bf9c..9e3d8d617c 100644 --- a/src/components/base/b-remote-provider/b-remote-provider.ts +++ b/src/components/base/b-remote-provider/b-remote-provider.ts @@ -16,8 +16,7 @@ import iData, { component, prop, RequestError, RetryRequestFn } from 'components export * from 'components/super/i-data/i-data'; -const - $$ = symbolGenerator(); +const $$ = symbolGenerator(); @component() export default class bRemoteProvider extends iData { @@ -50,23 +49,20 @@ export default class bRemoteProvider extends iData { * @emits `change(db: CanUndef)` */ protected syncDBWatcher(value: CanUndef): void { - const - parent = this.$parent; + const parent = this.$parent; if (parent == null) { return; } - const - fieldToUpdate = this.fieldProp; + const fieldToUpdate = this.fieldProp; let needUpdate = fieldToUpdate == null, action: Function; if (fieldToUpdate != null) { - const - field = parent.field.get(fieldToUpdate); + const field = parent.field.get(fieldToUpdate); if (Object.isFunction(field)) { action = () => field.call(parent, value); @@ -102,7 +98,7 @@ export default class bRemoteProvider extends iData { protected override onRequestError(err: Error | RequestError, retry: RetryRequestFn): void { const a = this.getPassedHandlers?.(); - if (a != null && (a.has('error') || a.has('error:component') || a.has('onError'))) { + if (a == null || !a.has('error') && !a.has('error:component') && !a.has('onError')) { super.onRequestError(err, retry); } From 099f6b011baa0e4ad37780776d08eea524b74bd2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 15:01:08 +0300 Subject: [PATCH 190/334] chore: updated tests --- .../test/unit/functional/props/props.ts | 4 ++-- .../test/unit/functional/state/emitter.ts | 18 +++++++++--------- .../test/unit/scenario/last-render.ts | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts index f473fdac40..33e2fa3103 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/props/props.ts @@ -287,9 +287,9 @@ test.describe('', () => { chunkSize, request: {get: {test: 1}}, '@hook:beforeDataCreate': (ctx) => { - const original = ctx.emit; + const original = ctx.strictEmit; - ctx.emit = jestMock.mock((...args) => { + ctx.strictEmit = jestMock.mock((...args) => { original(...args); return [args[0], Object.fastClone(ctx.getVirtualScrollState())]; }); diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts index b1c1e8e231..b718dd5895 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts @@ -45,7 +45,7 @@ test.describe('', () => { }); test.describe('all data has been loaded after the initial load', () => { - test('state at the time of emitting events must be correct', async () => { + test.only('state at the time of emitting events must be correct', async () => { const chunkSize = 12; const states = [ @@ -79,9 +79,9 @@ test.describe('', () => { chunkSize, shouldStopRequestingData: () => true, '@hook:beforeDataCreate': (ctx) => { - const original = ctx.emit; + const original = ctx.strictEmit; - ctx.emit = jestMock.mock((...args) => { + ctx.strictEmit = jestMock.mock((...args) => { original(...args); return [args[0], Object.fastClone(ctx.getVirtualScrollState())]; }); @@ -163,9 +163,9 @@ test.describe('', () => { .withProps({ chunkSize, '@hook:beforeDataCreate': (ctx) => { - const original = ctx.emit; + const original = ctx.strictEmit; - ctx.emit = jestMock.mock((...args) => { + ctx.strictEmit = jestMock.mock((...args) => { original(...args); return [args[0], Object.fastClone(ctx.getVirtualScrollState())]; }); @@ -295,9 +295,9 @@ test.describe('', () => { Object.get(state, 'lastLoadedRawData.total') === state.data.length, '@hook:beforeDataCreate': (ctx) => { - const original = ctx.emit; + const original = ctx.strictEmit; - ctx.emit = jestMock.mock((...args) => { + ctx.strictEmit = jestMock.mock((...args) => { original(...args); return [args[0], Object.fastClone(ctx.getVirtualScrollState())]; }); @@ -382,9 +382,9 @@ test.describe('', () => { items: state.data.getDataChunk(0), '@hook:beforeDataCreate': (ctx) => { - const original = ctx.emit; + const original = ctx.strictEmit; - ctx.emit = jestMock.mock((...args) => { + ctx.strictEmit = jestMock.mock((...args) => { original(...args); return [args[0], Object.fastClone(ctx.getVirtualScrollState())]; }); diff --git a/src/components/base/b-virtual-scroll-new/test/unit/scenario/last-render.ts b/src/components/base/b-virtual-scroll-new/test/unit/scenario/last-render.ts index 8e58b883c9..32911485fc 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/scenario/last-render.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/scenario/last-render.ts @@ -101,9 +101,9 @@ test.describe('', () => { shouldStopRequestingData: (state: VirtualScrollState): boolean => state.lastLoadedData.length === 0, '@hook:beforeDataCreate': (ctx) => { - const original = ctx.emit; + const original = ctx.strictEmit; - ctx.emit = jestMock.mock((...args) => { + ctx.strictEmit = jestMock.mock((...args) => { original(...args); return [args[0], Object.fastClone(ctx.getVirtualScrollState())]; }); From 7dcfd7700d0ba767a76498a001098ad62db96803 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 15:01:45 +0300 Subject: [PATCH 191/334] chore: removed only --- .../b-virtual-scroll-new/test/unit/functional/state/emitter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts index b718dd5895..8bfdd544d8 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts @@ -45,7 +45,7 @@ test.describe('', () => { }); test.describe('all data has been loaded after the initial load', () => { - test.only('state at the time of emitting events must be correct', async () => { + test('state at the time of emitting events must be correct', async () => { const chunkSize = 12; const states = [ From 533f46763eab7cfc1ef2f6c3a5cbe86b219e5e3b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 15:03:21 +0300 Subject: [PATCH 192/334] chore: updated tests --- .../test/unit/functional/state/emitter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts b/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts index 8bfdd544d8..cb0b1d486a 100644 --- a/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts +++ b/src/components/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts @@ -452,9 +452,9 @@ test.describe('', () => { state.reset(); await component.evaluate((ctx) => { - const original = Object.cast(ctx.emit); + const original = Object.cast(ctx.strictEmit); - ctx.emit = jestMock.mock((...args) => { + ctx.strictEmit = jestMock.mock((...args) => { original(...args); return [args[0], Object.fastClone(ctx.getVirtualScrollState())]; }); From 78b814c10713ce6d353e969bcc770f2f5e808533 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 15:27:10 +0300 Subject: [PATCH 193/334] chore: removed redundant normalization --- src/core/component/watch/bind.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index ddac823fa4..2fa0712bb9 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -101,12 +101,12 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR if (pathToEmitter !== '') { watcherCtx = Object.get(component, pathToEmitter) ?? Object.get(globalThis, pathToEmitter) ?? component; - watchPath = customWatcher[3].toString(); } else { watcherCtx = component; - watchPath = customWatcher[3].dasherize(); } + + watchPath = customWatcher[3]; } let propInfo: typeof p.info = p.info; From c8f8c659aefda48f1847a571287a9d1b41cebd0b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 15:27:30 +0300 Subject: [PATCH 194/334] chore: optimization --- src/components/super/i-block/event/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index fdec2b9f98..d0ef0c1019 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -673,5 +673,9 @@ function getComponentEventName(event: string): string { } function normalizeEventName(event: string): string { - return event.camelize(false); + if (event.includes('-')) { + return event.camelize(false); + } + + return event; } From d8660b08c2b5ca7376b5204aa5c0a1c13794466b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 15:34:22 +0300 Subject: [PATCH 195/334] fix: fixes after refactoring --- src/core/component/prop/helpers.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index deb692c86a..a7cd818845 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -40,9 +40,11 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { const propPrefix = 'on:'; if (meta.props[attrName]?.forceUpdate === false) { - const getterName = propPrefix + attrName; + const + getterName = propPrefix + attrName, + getterVal = unsafe.$attrs[getterName]; - if (!Object.isFunction(unsafe.$attrs[getterName])) { + if (getterVal !== undefined && !Object.isFunction(getterVal)) { throw new Error(`No accessors are defined for the prop "${attrName}". To set the accessors, pass them as ":${attrName} = propValue | @:${attrName} = createPropAccessors(() => propValue)()" or "v-attrs = {'@:${attrName}': createPropAccessors(() => propValue)}".`); } } From 6a8322e2748faea1888e70a03c8fe00d878134c7 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 15:42:31 +0300 Subject: [PATCH 196/334] fix: reverted br --- src/components/super/i-block/event/index.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index d0ef0c1019..4280a29e67 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -389,7 +389,7 @@ export default abstract class iBlockEvent extends iBlockBase { this.dispatch(event, ...args); } - if (!Object.isString(event)) { + if (this.verbose || eventDecl.logLevel !== 'info') { const logArgs = args.slice(); if (eventDecl.logLevel === 'error') { @@ -469,17 +469,15 @@ export default abstract class iBlockEvent extends iBlockBase { let {globalName, componentName, $parent: parent} = this; const - log = Object.isString(event), + log = this.verbose || eventDecl.logLevel !== 'info', logArgs = log ? args.slice() : args; - if (log) { - if (eventDecl.logLevel === 'error') { - logArgs.forEach((el, i) => { - if (Object.isFunction(el)) { - logArgs[i] = () => el; - } - }); - } + if (log && eventDecl.logLevel === 'error') { + logArgs.forEach((el, i) => { + if (Object.isFunction(el)) { + logArgs[i] = () => el; + } + }); } while (parent != null) { From 7094997ff8cc4f05a13982620ed00ba9ad9ee4c4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 15:54:06 +0300 Subject: [PATCH 197/334] fix: fixes after refactoring --- src/core/component/prop/helpers.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index a7cd818845..3d675c53ea 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -40,11 +40,9 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { const propPrefix = 'on:'; if (meta.props[attrName]?.forceUpdate === false) { - const - getterName = propPrefix + attrName, - getterVal = unsafe.$attrs[getterName]; + const getterName = propPrefix + attrName; - if (getterVal !== undefined && !Object.isFunction(getterVal)) { + if (unsafe.$attrs[attrName] !== undefined && !Object.isFunction(unsafe.$attrs[getterName])) { throw new Error(`No accessors are defined for the prop "${attrName}". To set the accessors, pass them as ":${attrName} = propValue | @:${attrName} = createPropAccessors(() => propValue)()" or "v-attrs = {'@:${attrName}': createPropAccessors(() => propValue)}".`); } } From 8b04aef8155c6c5035c714f957e9e7cf23e970cf Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 16:16:27 +0300 Subject: [PATCH 198/334] fix: updated tests --- src/components/super/i-block/event/test/unit/api.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/super/i-block/event/test/unit/api.ts b/src/components/super/i-block/event/test/unit/api.ts index d70a1b84b1..8578053663 100644 --- a/src/components/super/i-block/event/test/unit/api.ts +++ b/src/components/super/i-block/event/test/unit/api.ts @@ -28,10 +28,6 @@ test.describe(' event API', () => { const scan = await target.evaluate((ctx) => { const res: any[] = []; - ctx.on('onFoo_bar', (...args: any[]) => { - res.push(...args); - }); - ctx.on('onFoo-bar', (...args: any[]) => { res.push(...args); }); @@ -40,12 +36,12 @@ test.describe(' event API', () => { res.push(...args); }); - ctx.emit('foo bar', 1); + ctx.emit('foo-bar', 1); return res; }); - test.expect(scan).toEqual([1, 1, 1]); + test.expect(scan).toEqual([1, 1]); }); test('the `emit` method should fire 3 events', async ({page}) => { From fa8e7b23da4f175ac4d7e2aa58b9ccff2cefc87e Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 3 Oct 2024 17:05:19 +0300 Subject: [PATCH 199/334] fix: init dependencies on created --- src/core/component/accessor/index.ts | 160 ++++++++++++++------------- 1 file changed, 82 insertions(+), 78 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index b6ef8c30ac..ffd6702805 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -21,7 +21,7 @@ import { getPropertyInfo } from 'core/component/reflect'; import { getFieldsStore } from 'core/component/field'; import { cacheStatus } from 'core/component/watch'; -import type { ComponentInterface } from 'core/component/interface'; +import type { ComponentInterface, Hook } from 'core/component/interface'; /** * Attaches accessors and computed fields from a component's tied metaobject to the specified component instance. @@ -123,36 +123,38 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const {watchers, watchDependencies} = meta; - // If a computed property has a field or system field as a dependency - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - watchDependencies.get(name)?.forEach((dep) => { - const - path = Object.isArray(dep) ? dep.join('.') : String(dep), - info = getPropertyInfo(path, component); + onCreated(this.hook, () => { + // If a computed property has a field or system field as a dependency + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + watchDependencies.get(name)?.forEach((dep) => { + const + path = Object.isArray(dep) ? dep.join('.') : String(dep), + info = getPropertyInfo(path, component); - const needToForceWatching = - (info.type === 'system' || info.type === 'field') && + const needForceWatch = + (info.type === 'system' || info.type === 'field') && - watchers[info.name] == null && - watchers[info.originalPath] == null && - watchers[info.path] == null; + watchers[info.name] == null && + watchers[info.originalPath] == null && + watchers[info.path] == null; - if (needToForceWatching) { - watch.call(this, info, {deep: true, immediate: true}, fakeHandler); - } - }); + if (needForceWatch) { + this.$watch(info, {deep: true, immediate: true}, fakeHandler); + } + }); - if (tiedWith != null) { - const needToForceWatching = watchers[tiedWith] == null && accessor.dependencies?.length !== 0; + if (tiedWith != null) { + const needForceWatch = watchers[tiedWith] == null && accessor.dependencies?.length !== 0; - // If a computed property is tied with a field or system field - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - if (needToForceWatching) { - watch.call(this, tiedWith, {deep: true, immediate: true}, fakeHandler); + // If a computed property is tied with a field or system field + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + if (needForceWatch) { + this.$watch(tiedWith, {deep: true, immediate: true}, fakeHandler); + } } - } + }); } return accessor.get!.call(this); @@ -211,64 +213,66 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const {watchers, watchDependencies} = meta; - // If a computed property has a field or system field as a dependency - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - watchDependencies.get(name)?.forEach((dep) => { - const - path = Object.isArray(dep) ? dep.join('.') : String(dep), - info = getPropertyInfo(path, component); - - // If a getter already has a cached result and is used inside a template, - // it is not possible to track its effect, as the value is not recalculated. - // This can lead to a problem where one of the entities on which the getter depends is updated, - // but the template is not. - // To avoid this problem, we explicitly touch all dependent entities. - // For functional components, this problem does not exist, - // as no change in state can trigger their re-render. - if (!isFunctional && info.type !== 'system') { - effects.push(() => { - const store = info.type === 'field' ? getFieldsStore(Object.cast(info.ctx)) : info.ctx; + onCreated(this.hook, () => { + // If a computed property has a field or system field as a dependency + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + watchDependencies.get(name)?.forEach((dep) => { + const + path = Object.isArray(dep) ? dep.join('.') : String(dep), + info = getPropertyInfo(path, component); + + // If a getter already has a cached result and is used inside a template, + // it is not possible to track its effect, as the value is not recalculated. + // This can lead to a problem where one of the entities on which the getter depends is updated, + // but the template is not. + // To avoid this problem, we explicitly touch all dependent entities. + // For functional components, this problem does not exist, + // as no change in state can trigger their re-render. + if (!isFunctional && info.type !== 'system') { + effects.push(() => { + const store = info.type === 'field' ? getFieldsStore(Object.cast(info.ctx)) : info.ctx; + + if (info.path.includes('.')) { + void Object.get(store, path); + + } else if (path in store) { + // @ts-ignore (effect) + void store[path]; + } + }); + } + + const needToForceWatching = + (info.type === 'system' || info.type === 'field') && - if (info.path.includes('.')) { - void Object.get(store, path); + watchers[info.name] == null && + watchers[info.originalPath] == null && + watchers[info.path] == null; - } else if (path in store) { + if (needToForceWatching) { + this.$watch(info, {deep: true, immediate: true}, fakeHandler); + } + }); + + if (tiedWith != null) { + effects.push(() => { + if (tiedWith in this) { // @ts-ignore (effect) - void store[path]; + void this[tiedWith]; } }); - } - - const needToForceWatching = - (info.type === 'system' || info.type === 'field') && - watchers[info.name] == null && - watchers[info.originalPath] == null && - watchers[info.path] == null; - - if (needToForceWatching) { - watch.call(this, info, {deep: true, immediate: true}, fakeHandler); - } - }); + const needToForceWatching = watchers[tiedWith] == null && computed.dependencies?.length !== 0; - if (tiedWith != null) { - effects.push(() => { - if (tiedWith in this) { - // @ts-ignore (effect) - void this[tiedWith]; + // If a computed property is tied with a field or system field + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + if (needToForceWatching) { + this.$watch(tiedWith, {deep: true, immediate: true}, fakeHandler); } - }); - - const needToForceWatching = watchers[tiedWith] == null && computed.dependencies?.length !== 0; - - // If a computed property is tied with a field or system field - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - if (needToForceWatching) { - watch.call(this, tiedWith, {deep: true, immediate: true}, fakeHandler); } - } + }); } // We should not use the getter's cache until the component is fully created. @@ -334,12 +338,12 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { // Loopback } - function watch(this: typeof component, ...args: Parameters) { - if (beforeHooks[this.hook] != null) { - hooks['before:created'].push({fn: () => this.$watch(...args)}); + function onCreated(hook: Hook, cb: Function) { + if (beforeHooks[hook] != null) { + hooks['before:created'].push({fn: cb}); } else { - this.$watch(...args); + cb(); } } } From 13f1e01ab2f55456b48b50a47bd87fda9850a6a8 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 4 Oct 2024 16:35:55 +0300 Subject: [PATCH 200/334] chore: optimization --- src/core/component/field/const.ts | 15 -- src/core/component/field/index.ts | 1 - src/core/component/field/init.ts | 49 +--- src/core/component/field/interface.ts | 11 - .../component/init/states/before-create.ts | 2 +- .../init/states/before-data-create.ts | 2 +- src/core/component/meta/create.ts | 6 +- src/core/component/meta/field/index.ts | 91 ++++++++ .../{field/helpers.ts => meta/field/sort.ts} | 34 ++- src/core/component/meta/fill.ts | 35 ++- src/core/component/meta/interface/index.ts | 196 +--------------- src/core/component/meta/interface/meta.ts | 213 ++++++++++++++++++ src/core/component/meta/interface/types.ts | 8 + 13 files changed, 356 insertions(+), 307 deletions(-) delete mode 100644 src/core/component/field/const.ts delete mode 100644 src/core/component/field/interface.ts create mode 100644 src/core/component/meta/field/index.ts rename src/core/component/{field/helpers.ts => meta/field/sort.ts} (68%) create mode 100644 src/core/component/meta/interface/meta.ts diff --git a/src/core/component/field/const.ts b/src/core/component/field/const.ts deleted file mode 100644 index cec2b48a8d..0000000000 --- a/src/core/component/field/const.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import type { ComponentField } from 'core/component/interface'; -import type { SortedFields } from 'core/component/field/interface'; - -/** - * A cache for sorted component fields - */ -export const sortedFields = new WeakMap, SortedFields>(); diff --git a/src/core/component/field/index.ts b/src/core/component/field/index.ts index 2bbbec88f6..531a4cb11e 100644 --- a/src/core/component/field/index.ts +++ b/src/core/component/field/index.ts @@ -13,4 +13,3 @@ export * from 'core/component/field/init'; export * from 'core/component/field/store'; -export * from 'core/component/field/interface'; diff --git a/src/core/component/field/init.ts b/src/core/component/field/init.ts index 567cc03d4f..0234ffd48b 100644 --- a/src/core/component/field/init.ts +++ b/src/core/component/field/init.ts @@ -6,8 +6,8 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { sortFields } from 'core/component/field/helpers'; -import type { ComponentInterface, ComponentField } from 'core/component/interface'; +import type { ComponentMeta } from 'core/component/meta'; +import type { ComponentInterface } from 'core/component/interface'; /** * Initializes all fields of a given component instance. @@ -19,7 +19,7 @@ import type { ComponentInterface, ComponentField } from 'core/component/interfac * @param [store] - the store for initialized fields */ export function initFields( - from: Dictionary, + from: ComponentMeta['fieldInitializers'], component: ComponentInterface, store: Dictionary = {} ): Dictionary { @@ -27,55 +27,20 @@ export function initFields( component ); - const { - params, - instance - } = unsafe.meta; - - const isFunctional = params.functional === true; - - sortFields(from).forEach(([name, field]) => { + from.forEach(([name, init]) => { const sourceVal = store[name]; - const canSkip = - field == null || sourceVal !== undefined || - !SSR && isFunctional && field.functional === false || - field.init == null && field.default === undefined && instance[name] === undefined; - - if (field == null || canSkip) { + if (init == null) { store[name] = sourceVal; return; } unsafe.$activeField = name; - let val: unknown; - - if (field.init != null) { - val = field.init(component.unsafe, store); - } - - if (val === undefined) { - if (store[name] === undefined) { - // To prevent linking to the same type of component for non-primitive values, - // it's important to clone the default value from the component constructor. - if (field.default !== undefined) { - val = field.default; - - } else { - const defValue = instance[name]; - val = Object.isPrimitive(defValue) ? defValue : Object.fastClone(defValue); - } - - store[name] = val; - } + init(component, store); - } else { - store[name] = val; - } + unsafe.$activeField = undefined; }); - unsafe.$activeField = undefined; - return store; } diff --git a/src/core/component/field/interface.ts b/src/core/component/field/interface.ts deleted file mode 100644 index d8b501cb03..0000000000 --- a/src/core/component/field/interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import type { ComponentField } from 'core/component/interface'; - -export type SortedFields = Array<[string, CanUndef]>; diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 31f813a8a6..049fc18feb 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -285,7 +285,7 @@ export function beforeCreateState( runHook('beforeRuntime', component).catch(stderr); - initFields(meta.systemFields, component, unsafe); + initFields(meta.systemFieldInitializers, component, unsafe); runHook('beforeCreate', component).catch(stderr); callMethodFromComponent(component, 'beforeCreate'); diff --git a/src/core/component/init/states/before-data-create.ts b/src/core/component/init/states/before-data-create.ts index 3c2a1c1c27..560de43ac3 100644 --- a/src/core/component/init/states/before-data-create.ts +++ b/src/core/component/init/states/before-data-create.ts @@ -18,7 +18,7 @@ import type { ComponentInterface } from 'core/component/interface'; */ export function beforeDataCreateState(component: ComponentInterface): void { const {meta, $fields} = component.unsafe; - initFields(meta.fields, component, $fields); + initFields(meta.fieldInitializers, component, $fields); // In functional components, the watching of fields can be initialized in lazy mode if (SSR || meta.params.functional === true) { diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index b5bd7e2523..83788b5e2d 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -32,8 +32,12 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { mods: component.params.partial == null ? getComponentMods(component) : {}, fields: {}, - tiedFields: {}, + fieldInitializers: [], + systemFields: {}, + systemFieldInitializers: [], + + tiedFields: {}, computedFields: {}, methods: {}, diff --git a/src/core/component/meta/field/index.ts b/src/core/component/meta/field/index.ts new file mode 100644 index 0000000000..44706340f5 --- /dev/null +++ b/src/core/component/meta/field/index.ts @@ -0,0 +1,91 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { sortFields } from 'core/component/meta/field/sort'; + +import type { ComponentInterface } from 'core/component/interface'; +import type { ComponentMeta } from 'core/component/meta/interface'; + +/** + * Adds initialization functions to the metaobject for the given component field type, and also registers watchers + * + * @param type + * @param meta + */ +export function addFieldsToMeta(type: 'fields' | 'systemFields', meta: ComponentMeta): void { + const {watchers} = meta; + + const isFunctional = meta.params.functional === true; + + const + fields = meta[type], + fieldInitializers = type === 'fields' ? meta.fieldInitializers : meta.systemFieldInitializers; + + Object.entries(fields).forEach(([fieldName, field]) => { + if (field == null || !SSR && isFunctional && field.functional === false) { + return; + } + + let + getValue: CanNull = null, + getDefValue: CanNull = null; + + if (field.default !== undefined) { + getDefValue = () => field.default; + + } else if (meta.instance[fieldName] !== undefined) { + const val = meta.instance[fieldName]; + + if (Object.isPrimitive(val)) { + getDefValue = () => val; + + } else { + // To prevent linking to the same type of component for non-primitive values, + // it's important to clone the default value from the component constructor. + getDefValue = () => Object.fastClone(val); + } + } + + if (field.init != null) { + getValue = (ctx: ComponentInterface, store: Dictionary) => { + const val = field.init!(ctx.unsafe, store); + + if (val === undefined && getDefValue != null) { + if (store[fieldName] === undefined) { + store[fieldName] = getDefValue(); + } + + } else { + store[fieldName] = val; + } + }; + + } else if (getDefValue != null) { + getValue = (_: ComponentInterface, store: Dictionary) => { + if (store[fieldName] === undefined) { + store[fieldName] = getDefValue!(); + } + }; + } + + fieldInitializers.push([fieldName, getValue]); + + field.watchers?.forEach((watcher) => { + if (isFunctional && watcher.functional === false) { + return; + } + + const watcherListeners = watchers[fieldName] ?? []; + watchers[fieldName] = watcherListeners; + + watcherListeners.push(watcher); + }); + }); + + sortFields(fieldInitializers, fields); +} diff --git a/src/core/component/field/helpers.ts b/src/core/component/meta/field/sort.ts similarity index 68% rename from src/core/component/field/helpers.ts rename to src/core/component/meta/field/sort.ts index edcf59c9d9..1cabc3e046 100644 --- a/src/core/component/field/helpers.ts +++ b/src/core/component/meta/field/sort.ts @@ -6,10 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { sortedFields } from 'core/component/field/const'; - -import type { ComponentField } from 'core/component/interface'; -import type { SortedFields } from 'core/component/field/interface'; +import type { ComponentField, ComponentFieldInitializers } from 'core/component/meta'; /** * Returns the weight of a specified field from a given scope. @@ -53,22 +50,19 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar /** * Sorts the specified fields and returns an array that is ordered and ready for initialization + * * @param fields + * @param scope */ -export function sortFields(fields: Dictionary): SortedFields { - let val = sortedFields.get(fields); - - if (val == null) { - val = Object.entries(Object.cast>(fields)).sort(([_1, a], [_2, b]) => { - const - aWeight = getFieldWeight(a, fields), - bWeight = getFieldWeight(b, fields); - - return aWeight - bWeight; - }); - - sortedFields.set(fields, val); - } - - return val; +export function sortFields( + fields: ComponentFieldInitializers, + scope: Dictionary +): ComponentFieldInitializers { + return fields.sort(([aName], [bName]) => { + const + aWeight = getFieldWeight(scope[aName], scope), + bWeight = getFieldWeight(scope[bName], scope); + + return aWeight - bWeight; + }); } diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 2590262ac1..bf253bad4d 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -10,9 +10,12 @@ import { DEFAULT_WRAPPER } from 'core/component/const'; import { getComponentContext } from 'core/component/context'; import { isAbstractComponent, isBinding } from 'core/component/reflect'; + +import { addFieldsToMeta } from 'core/component/meta/field'; import { addMethodsToMeta } from 'core/component/meta/method'; -import type { ComponentConstructor, ComponentMeta, ModVal } from 'core/component/interface'; +import type { ComponentConstructor, ModVal } from 'core/component/interface'; +import type { ComponentMeta } from 'core/component/meta/interface'; const INSTANCE = Symbol('The component instance'), @@ -39,12 +42,16 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor Object.defineProperty(meta, BLUEPRINT, { value: { watchers: meta.watchers, - hooks: meta.hooks + hooks: meta.hooks, + fieldInitializers: meta.fieldInitializers, + systemFieldInitializers: meta.systemFieldInitializers } }); } - const blueprint: CanNull> = meta[BLUEPRINT]; + type Blueprint = Pick; + + const blueprint: CanNull = meta[BLUEPRINT]; if (blueprint != null) { const hooks = {}; @@ -55,7 +62,9 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor Object.assign(meta, { hooks, - watchers: {...blueprint.watchers} + watchers: {...blueprint.watchers}, + fieldInitializers: blueprint.fieldInitializers.slice(), + systemFieldInitializers: blueprint.systemFieldInitializers.slice() }); } @@ -194,22 +203,8 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor } }); - // Fields - - [meta.systemFields, meta.fields].forEach((field) => { - Object.entries(field).forEach(([fieldName, field]) => { - field?.watchers?.forEach((watcher) => { - if (isFunctional && watcher.functional === false) { - return; - } - - const watcherListeners = watchers[fieldName] ?? []; - watchers[fieldName] = watcherListeners; - - watcherListeners.push(watcher); - }); - }); - }); + addFieldsToMeta('fields', meta); + addFieldsToMeta('systemFields', meta); // Computed fields diff --git a/src/core/component/meta/interface/index.ts b/src/core/component/meta/interface/index.ts index 1bae1cdf63..d21f8f9e8d 100644 --- a/src/core/component/meta/interface/index.ts +++ b/src/core/component/meta/interface/index.ts @@ -6,200 +6,6 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type { WatchPath } from 'core/object/watch'; - -import type { PropOptions } from 'core/component/decorators'; -import type { RenderFunction, WritableComputedOptions } from 'core/component/engines'; - -import type { WatchObject, ComponentConstructor, ModsDecl } from 'core/component/interface'; -import type { ComponentOptions } from 'core/component/meta/interface/options'; - -import type { - - ComponentProp, - ComponentField, - - ComponentMethod, - ComponentAccessor, - ComponentHooks, - - ComponentDirectiveOptions - -} from 'core/component/meta/interface/types'; - +export * from 'core/component/meta/interface/meta'; export * from 'core/component/meta/interface/options'; export * from 'core/component/meta/interface/types'; - -/** - * An abstract component representation - */ -export interface ComponentMeta { - /** - * The full name of the component, which may include a `-functional` postfix if the component is smart - */ - name: string; - - /** - * The name of the NPM package in which the component is defined or overridden - */ - layer?: string; - - /** - * Component name without any special suffixes - */ - componentName: string; - - /** - * A link to the component's constructor - */ - constructor: ComponentConstructor; - - /** - * A link to the component's class instance - */ - instance: Dictionary; - - /** - * A dictionary containing the parameters provided to the `@component` decorator for the component - */ - params: ComponentOptions; - - /** - * A link to the metaobject of the parent component - */ - parentMeta: CanNull; - - /** - * A dictionary containing the input properties (props) for the component - */ - props: Dictionary; - - /** - * A dictionary containing the available component modifiers. - * Modifiers are a way to alter the behavior or appearance of a component without changing its underlying - * functionality. - * They can be used to customize components for specific use cases, or to extend their capabilities. - * The modifiers may include options such as size, color, placement, and other configurations. - */ - mods: ModsDecl; - - /** - * A dictionary containing the component fields that can trigger a re-rendering of the component - */ - fields: Dictionary; - - /** - * A dictionary containing the component fields that do not cause a re-rendering of the component when they change. - * These fields are typically used for internal bookkeeping or for caching computed values, - * and do not affect the visual representation of the component. - * Examples include variables used for storing data or for tracking the component's internal state, - * and helper functions or methods that do not modify any reactive properties. - * It's important to identify and distinguish these non-reactive fields from the reactive ones, - * and to use them appropriately to optimize the performance of the component. - */ - systemFields: Dictionary; - - /** - * A dictionary containing the component properties as well as properties that are related to them. - * For example: - * - * `foo → fooStore` - * `fooStore → foo` - */ - tiedFields: Dictionary; - - /** - * A dictionary containing the accessor methods of the component that support caching or watching - */ - computedFields: Dictionary; - - /** - * A dictionary containing the simple component accessors, - * which are typically used for retrieving or modifying the value of a non-reactive property - * that does not require caching or watching - */ - accessors: Dictionary; - - /** - * A dictionary containing the component methods - */ - methods: Dictionary; - - /** - * A dictionary containing the component watchers - */ - watchers: Dictionary; - - /** - * A dictionary containing the component dependencies to watch to invalidate the cache of computed fields - */ - watchDependencies: Map; - - /** - * A dictionary containing the component prop dependencies to watch to invalidate the cache of computed fields - */ - watchPropDependencies: Map>; - - /** - * A dictionary containing the component hook listeners, - * which are essentially functions that are executed at specific stages in the V4Fire component's lifecycle - */ - hooks: ComponentHooks; - - /** - * A less abstract representation of the component would typically include the following elements, - * which are useful for building component libraries: - */ - component: { - /** - * The full name of the component, which may include a `-functional` postfix if the component is smart - */ - name: string; - - /** - * A dictionary with registered component props - */ - props: Dictionary; - - /** - * A dictionary with registered component attributes. - * Unlike props, changing attributes does not lead to re-rendering of the component template. - */ - attrs: Dictionary; - - /** - * A dictionary containing the default component modifiers - */ - mods: Dictionary; - - /** - * A dictionary containing the accessor methods of the component that support caching or watching - */ - computed: Dictionary>>; - - /** - * A dictionary containing the component methods - */ - methods: Dictionary; - - /** - * A dictionary containing the available component directives - */ - directives?: Dictionary; - - /** - * A dictionary containing the available local components - */ - components?: Dictionary; - - /** - * The component's render function - */ - render?: RenderFunction; - - /** - * The component's render function for use with SSR - */ - ssrRender?: RenderFunction; - }; -} diff --git a/src/core/component/meta/interface/meta.ts b/src/core/component/meta/interface/meta.ts new file mode 100644 index 0000000000..43cc1e1176 --- /dev/null +++ b/src/core/component/meta/interface/meta.ts @@ -0,0 +1,213 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import type { WatchPath } from 'core/object/watch'; + +import type { PropOptions } from 'core/component/decorators'; +import type { RenderFunction, WritableComputedOptions } from 'core/component/engines'; + +import type { ComponentConstructor, WatchObject, ModsDecl } from 'core/component/interface'; +import type { ComponentOptions } from 'core/component/meta/interface/options'; + +import type { + + ComponentProp, + ComponentField, + ComponentFieldInitializers, + + ComponentMethod, + ComponentAccessor, + ComponentHooks, + + ComponentDirectiveOptions + +} from 'core/component/meta/interface/types'; + +/** + * An abstract component representation + */ +export interface ComponentMeta { + /** + * The full name of the component, which may include a `-functional` postfix if the component is smart + */ + name: string; + + /** + * The name of the NPM package in which the component is defined or overridden + */ + layer?: string; + + /** + * Component name without any special suffixes + */ + componentName: string; + + /** + * A link to the component's constructor + */ + constructor: ComponentConstructor; + + /** + * A link to the component's class instance + */ + instance: Dictionary; + + /** + * A dictionary containing the parameters provided to the `@component` decorator for the component + */ + params: ComponentOptions; + + /** + * A link to the metaobject of the parent component + */ + parentMeta: CanNull; + + /** + * A dictionary containing the input properties (props) for the component + */ + props: Dictionary; + + /** + * A dictionary containing the available component modifiers. + * Modifiers are a way to alter the behavior or appearance of a component without changing its underlying + * functionality. + * They can be used to customize components for specific use cases, or to extend their capabilities. + * The modifiers may include options such as size, color, placement, and other configurations. + */ + mods: ModsDecl; + + /** + * A dictionary containing the component fields that can trigger a re-rendering of the component + */ + fields: Dictionary; + + /** + * A sorted array of fields and functions for their initialization on the component + */ + fieldInitializers: ComponentFieldInitializers; + + /** + * A dictionary containing the component fields that do not cause a re-rendering of the component when they change. + * These fields are typically used for internal bookkeeping or for caching computed values, + * and do not affect the visual representation of the component. + * Examples include variables used for storing data or for tracking the component's internal state, + * and helper functions or methods that do not modify any reactive properties. + * It's important to identify and distinguish these non-reactive fields from the reactive ones, + * and to use them appropriately to optimize the performance of the component. + */ + systemFields: Dictionary; + + /** + * A sorted array of system fields and functions for their initialization on the component + */ + systemFieldInitializers: ComponentFieldInitializers; + + /** + * A dictionary containing the component properties as well as properties that are related to them. + * For example: + * + * `foo → fooStore` + * `fooStore → foo` + */ + tiedFields: Dictionary; + + /** + * A dictionary containing the accessor methods of the component that support caching or watching + */ + computedFields: Dictionary; + + /** + * A dictionary containing the simple component accessors, + * which are typically used for retrieving or modifying the value of a non-reactive property + * that does not require caching or watching + */ + accessors: Dictionary; + + /** + * A dictionary containing the component methods + */ + methods: Dictionary; + + /** + * A dictionary containing the component watchers + */ + watchers: Dictionary; + + /** + * A dictionary containing the component dependencies to watch to invalidate the cache of computed fields + */ + watchDependencies: Map; + + /** + * A dictionary containing the component prop dependencies to watch to invalidate the cache of computed fields + */ + watchPropDependencies: Map>; + + /** + * A dictionary containing the component hook listeners, + * which are essentially functions that are executed at specific stages in the V4Fire component's lifecycle + */ + hooks: ComponentHooks; + + /** + * A less abstract representation of the component would typically include the following elements, + * which are useful for building component libraries: + */ + component: { + /** + * The full name of the component, which may include a `-functional` postfix if the component is smart + */ + name: string; + + /** + * A dictionary with registered component props + */ + props: Dictionary; + + /** + * A dictionary with registered component attributes. + * Unlike props, changing attributes does not lead to re-rendering of the component template. + */ + attrs: Dictionary; + + /** + * A dictionary containing the default component modifiers + */ + mods: Dictionary; + + /** + * A dictionary containing the accessor methods of the component that support caching or watching + */ + computed: Dictionary>>; + + /** + * A dictionary containing the component methods + */ + methods: Dictionary; + + /** + * A dictionary containing the available component directives + */ + directives?: Dictionary; + + /** + * A dictionary containing the available local components + */ + components?: Dictionary; + + /** + * The component's render function + */ + render?: RenderFunction; + + /** + * The component's render function for use with SSR + */ + ssrRender?: RenderFunction; + }; +} diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index d591a2fd2f..d5196bb5a6 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -16,8 +16,10 @@ import type { ComponentInterface, FieldWatcher, MethodWatcher, Hook } from 'core export interface ComponentProp extends PropOptions { forceUpdate: boolean; forceDefault?: boolean; + watchers?: Map; default?: unknown; + meta: Dictionary; } @@ -43,6 +45,12 @@ export interface ComponentField; } +export interface ComponentFieldInitializer { + (ctx: ComponentInterface, store: Dictionary): unknown; +} + +export type ComponentFieldInitializers = Array<[string, CanNull]>; + export type ComponentAccessorCacheType = boolean | 'forever' | From 2b968a641ed3c791a658da5b5e512b9f9b7205db Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 8 Oct 2024 13:49:14 +0300 Subject: [PATCH 201/334] refactor: extract @watch from factory --- src/core/component/decorators/helpers.ts | 60 ++++++ .../component/decorators/interface/types.ts | 15 +- .../component/decorators/watch/decorator.ts | 199 ++++++++++++++++++ src/core/component/decorators/watch/index.ts | 117 +--------- .../component/decorators/watch/interface.ts | 87 ++++++++ 5 files changed, 359 insertions(+), 119 deletions(-) create mode 100644 src/core/component/decorators/helpers.ts create mode 100644 src/core/component/decorators/watch/decorator.ts create mode 100644 src/core/component/decorators/watch/interface.ts diff --git a/src/core/component/decorators/helpers.ts b/src/core/component/decorators/helpers.ts new file mode 100644 index 0000000000..144e6d635c --- /dev/null +++ b/src/core/component/decorators/helpers.ts @@ -0,0 +1,60 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { initEmitter } from 'core/component/event'; + +import { componentDecoratedKeys } from 'core/component/const'; + +import type { ComponentMeta } from 'core/component/meta'; + +import type { + + PartDecorator, + ComponentPartDecorator, + + DecoratorFunctionalOptions + +} from 'core/component/decorators/interface'; + +/** + * Creates a decorator for a component's property or method based on the provided decorator function + * @param decorator + */ +export function createComponentDecorator(decorator: ComponentPartDecorator): PartDecorator { + return (_: object, partKey: string, partDesc?: PropertyDescriptor) => { + initEmitter.once('bindConstructor', (componentName, regEvent) => { + const decoratedKeys = componentDecoratedKeys[componentName] ?? new Set(); + componentDecoratedKeys[componentName] = decoratedKeys; + + decoratedKeys.add(partKey); + + initEmitter.once(regEvent, (componentDesc) => { + decorator(componentDesc, partKey, partDesc); + }); + }); + }; +} + +/** + * Accepts decorator parameters and a component metaobject, + * and normalizes the value of the functional option based on these parameters + * + * @param params + * @param meta + */ +export function normalizeFunctionalParams( + params: T, + meta: ComponentMeta +): T { + // eslint-disable-next-line eqeqeq + if (params.functional === undefined && meta.params.functional === null) { + params.functional = false; + } + + return params; +} diff --git a/src/core/component/decorators/interface/types.ts b/src/core/component/decorators/interface/types.ts index 52658aa0be..1891b9bdfb 100644 --- a/src/core/component/decorators/interface/types.ts +++ b/src/core/component/decorators/interface/types.ts @@ -6,6 +6,8 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import type { ComponentMeta } from 'core/component/meta'; + export interface DecoratorFunctionalOptions { /** * If set to false, this value can't be used with a functional component @@ -14,10 +16,15 @@ export interface DecoratorFunctionalOptions { functional?: boolean; } -export interface ParamsFactoryTransformer { - (params: object, cluster: string): Dictionary; +interface ComponentDescriptor { + meta: ComponentMeta; + parentMeta: CanNull; +} + +export interface ComponentPartDecorator { + (component: ComponentDescriptor, partKey: string, partDesc?: PropertyDescriptor): void; } -export interface FactoryTransformer { - (params?: T): Function; +export interface PartDecorator { + (target: object, partKey: string, partDesc?: PropertyDescriptor): void; } diff --git a/src/core/component/decorators/watch/decorator.ts b/src/core/component/decorators/watch/decorator.ts new file mode 100644 index 0000000000..554f303df8 --- /dev/null +++ b/src/core/component/decorators/watch/decorator.ts @@ -0,0 +1,199 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; + +import type { PartDecorator } from 'core/component/decorators/interface'; + +import type { + + DecoratorWatcher, + DecoratorMethodWatcher, + DecoratorFieldWatcher + +} from 'core/component/decorators/watch/interface'; + +/** + * Attaches a watcher of a component property/event to a component method or property. + * + * When you observe a property's alteration, + * the handler function can accept a second argument that refers to the property's old value. + * If the object being watched isn't primitive, the old value will be cloned from the original old value. + * This helps avoid issues that may arise from having two references to the same object. + * + * ```typescript + * import iBlock, { component, field, watch } from 'components/super/i-block/i-block'; + * + * @component() + * class Foo extends iBlock { + * @field() + * list: Dictionary[] = []; + * + * @watch('list') + * onListChange(value: Dictionary[], oldValue: Dictionary[]): void { + * // true + * console.log(value !== oldValue); + * console.log(value[0] !== oldValue[0]); + * } + * + * // If you don't specify a second argument in a watcher, + * // the property's old value won't be cloned. + * @watch('list') + * onListChangeWithoutCloning(value: Dictionary[]): void { + * // true + * console.log(value === arguments[1]); + * console.log(value[0] === oldValue[0]); + * } + * + * // If you're deep-watching a property and declare a second argument in a watcher, + * // the old value of the property will be deep-cloned. + * @watch({path: 'list', deep: true}) + * onListChangeWithDeepCloning(value: Dictionary[], oldValue: Dictionary[]): void { + * // true + * console.log(value !== oldValue); + * console.log(value[0] !== oldValue[0]); + * } + * + * created() { + * this.list.push({}); + * this.list[0].foo = 1; + * } + * } + * ``` + * + * To listen to an event, you should use the special delimiter `:` within a watch path. + * You can also specify an event emitter to listen to by writing a link before `:`. + * Here are some examples: + * + * 1. `:onChange` - the component will listen to its own event `onChange`. + * 2. `localEmitter:onChange` - the component will listen to an `onChange` event from `localEmitter`. + * 3. `$parent.localEmitter:onChange` - the component will listen to an `onChange` event from `$parent.localEmitter`. + * 4. `document:scroll` - the component will listen to a `scroll` event from `window.document`. + * + * A link to the event emitter is taken either from the component properties or from the global object. + * An empty link `''` refers to the component itself. + * + * If you are listening to an event, you can manage when to start listening to the event by using special characters at + * the beginning of a watch path: + * + * 1. `'!'` - start to listen to an event on the `beforeCreate` hook, e.g., `!rootEmitter:reset`. + * 2. `'?'` - start to listen to an event on the `mounted` hook, e.g., `?$el:click`. + * + * By default, all events start being listened to on the `created` hook. + * + * ```typescript + * import iBlock, { component, field, watch } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @field() + * foo: Dictionary = {bla: 0}; + * + * // Watch "foo" for any changes + * @watch('foo') + * watcher1() { + * + * } + * + * // Deeply watch "foo" for any changes + * @watch({path: 'foo', deep: true}) + * watcher2() { + * + * } + * + * // Watch "foo.bla" for any changes + * @watch('foo.bla') + * watcher3() { + * + * } + * + * // Listen to the component's "onChange" event + * @watch(':onChange') + * watcher3() { + * + * } + * + * // Listen to "onChange" event from the parentEmitter component + * @watch('parentEmitter:onChange') + * watcher4() { + * + * } + * } + * ``` + * + * @param watcher - parameters for observation + */ +export function watch(watcher: DecoratorWatcher): PartDecorator { + return createComponentDecorator(({meta}, key, desc?) => { + if (desc == null) { + decorateField(); + + } else { + decorateMethod(); + } + + function decorateMethod() { + const methodWatchers = Array.toArray(watcher); + + const method = meta.methods[key] ?? { + src: meta.componentName, + fn: Object.throw, + watchers: {} + }; + + const {watchers = {}} = method; + + for (const methodWatcher of methodWatchers) { + if (Object.isString(methodWatcher)) { + watchers[methodWatcher] = normalizeFunctionalParams({path: methodWatcher}, meta); + + } else { + watchers[methodWatcher.path] = normalizeFunctionalParams({...methodWatcher}, meta); + } + } + + meta.methods[key] = normalizeFunctionalParams({...method, watchers}, meta); + } + + function decorateField() { + const fieldWatchers = Array.toArray(watcher); + + let store: typeof meta['props'] | typeof meta['fields']; + + if (key in meta.props) { + store = meta.props; + + } else if (key in meta.fields) { + store = meta.fields; + + } else { + store = meta.systemFields; + } + + const field = store[key] ?? { + src: meta.componentName, + handler: Object.throw, + watchers: new Map(), + meta: {} + }; + + const {watchers = new Map()} = field; + + for (const fieldWatcher of fieldWatchers) { + if (Object.isPlainObject(fieldWatcher)) { + watchers.set(fieldWatcher.handler, normalizeFunctionalParams({...fieldWatcher}, meta)); + + } else { + watchers.set(watcher, normalizeFunctionalParams({handler: watcher}, meta)); + } + } + + store[key] = normalizeFunctionalParams({...field, watchers}, meta); + } + }); +} diff --git a/src/core/component/decorators/watch/index.ts b/src/core/component/decorators/watch/index.ts index 43c696941a..a536160ded 100644 --- a/src/core/component/decorators/watch/index.ts +++ b/src/core/component/decorators/watch/index.ts @@ -11,118 +11,5 @@ * @packageDocumentation */ -import { paramsFactory } from 'core/component/decorators/factory'; -import type { DecoratorFieldWatcher, DecoratorMethodWatcher } from 'core/component/decorators/interface'; - -/** - * Attaches a watcher of a component property/event to a component method or property. - * - * When you observe a property's alteration, - * the handler function can accept a second argument that refers to the property's old value. - * If the object being watched isn't primitive, the old value will be cloned from the original old value. - * This helps avoid issues that may arise from having two references to the same object. - * - * ```typescript - * import iBlock, { component, field, watch } from 'components/super/i-block/i-block'; - * - * @component() - * class Foo extends iBlock { - * @field() - * list: Dictionary[] = []; - * - * @watch('list') - * onListChange(value: Dictionary[], oldValue: Dictionary[]): void { - * // true - * console.log(value !== oldValue); - * console.log(value[0] !== oldValue[0]); - * } - * - * // If you don't specify a second argument in a watcher, - * // the property's old value won't be cloned. - * @watch('list') - * onListChangeWithoutCloning(value: Dictionary[]): void { - * // true - * console.log(value === arguments[1]); - * console.log(value[0] === oldValue[0]); - * } - * - * // If you're deep-watching a property and declare a second argument in a watcher, - * // the old value of the property will be deep-cloned. - * @watch({path: 'list', deep: true}) - * onListChangeWithDeepCloning(value: Dictionary[], oldValue: Dictionary[]): void { - * // true - * console.log(value !== oldValue); - * console.log(value[0] !== oldValue[0]); - * } - * - * created() { - * this.list.push({}); - * this.list[0].foo = 1; - * } - * } - * ``` - * - * To listen to an event, you should use the special delimiter `:` within a watch path. - * You can also specify an event emitter to listen to by writing a link before `:`. - * Here are some examples: - * - * 1. `:onChange` - the component will listen to its own event `onChange`. - * 2. `localEmitter:onChange` - the component will listen to an `onChange` event from `localEmitter`. - * 3. `$parent.localEmitter:onChange` - the component will listen to an `onChange` event from `$parent.localEmitter`. - * 4. `document:scroll` - the component will listen to a `scroll` event from `window.document`. - * - * A link to the event emitter is taken either from the component properties or from the global object. - * An empty link `''` refers to the component itself. - * - * If you are listening to an event, you can manage when to start listening to the event by using special characters at - * the beginning of a watch path: - * - * 1. `'!'` - start to listen to an event on the `beforeCreate` hook, e.g., `!rootEmitter:reset`. - * 2. `'?'` - start to listen to an event on the `mounted` hook, e.g., `?$el:click`. - * - * By default, all events start being listened to on the `created` hook. - * - * ```typescript - * import iBlock, { component, field, watch } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @field() - * foo: Dictionary = {bla: 0}; - * - * // Watch "foo" for any changes - * @watch('foo') - * watcher1() { - * - * } - * - * // Deeply watch "foo" for any changes - * @watch({path: 'foo', deep: true}) - * watcher2() { - * - * } - * - * // Watch "foo.bla" for any changes - * @watch('foo.bla') - * watcher3() { - * - * } - * - * // Listen to the component's "onChange" event - * @watch(':onChange') - * watcher3() { - * - * } - * - * // Listen to "onChange" event from the parentEmitter component - * @watch('parentEmitter:onChange') - * watcher4() { - * - * } - * } - * ``` - */ -export const watch = paramsFactory< - DecoratorFieldWatcher | - DecoratorMethodWatcher ->(null, (watch) => ({watch})); +export * from 'core/component/decorators/watch/decorator'; +export * from 'core/component/decorators/watch/interface'; diff --git a/src/core/component/decorators/watch/interface.ts b/src/core/component/decorators/watch/interface.ts new file mode 100644 index 0000000000..c16465f9cd --- /dev/null +++ b/src/core/component/decorators/watch/interface.ts @@ -0,0 +1,87 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import type { ComponentInterface, MethodWatcher, WatchHandlerParams, WatchOptions } from 'core/component/interface'; + +export type DecoratorWatcher = + DecoratorMethodWatcher | + DecoratorFieldWatcher; + +export type DecoratorMethodWatcher = + string | + MethodWatcher & {path: string} | + Array & {path: string}>; + +export type DecoratorFieldWatcher = + string | + DecoratorFieldWatcherObject | + DecoratorWatchHandler | + Array | DecoratorWatchHandler>; + +export interface DecoratorWatchHandler { + (ctx: Ctx['unsafe'], a: A, b: B, params?: WatchHandlerParams): unknown; + (ctx: Ctx['unsafe'], ...args: A[]): unknown; +} + +export interface DecoratorFieldWatcherObject< + Ctx extends ComponentInterface = ComponentInterface, + A = unknown, + B = A +> extends WatchOptions { + /** + * A function (or the name of a component method) that gets triggered on watcher events + * + * @example + * ```typescript + * import iBlock, { component, field, watch } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @watch({handler: 'onIncrement'}) + * @field() + * i: number = 0; + * + * onIncrement(val, oldVal, info) { + * console.log(val, oldVal, info); + * } + * } + * ``` + */ + handler: string | DecoratorWatchHandler; + + /** + * If set to false, a handler that gets invoked due to a watcher event won't take any arguments from the event + * + * @default `true` + * + * @example + * ```typescript + * import iBlock, { component, field } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @field({watch: {handler: 'onIncrement', provideArgs: false}}) + * i: number = 0; + * + * onIncrement(val) { + * console.log(val === undefined); + * } + * } + * ``` + */ + provideArgs?: boolean; + + /** + * A function to determine whether a watcher should be initialized or not. + * If the function returns false, the watcher will not be initialized. + * Useful for precise component optimizations. + * + * @param ctx + */ + shouldInit?(ctx: Ctx): boolean; +} From 540f298b5d0f462ed337675aeff26381ad2de6c8 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 8 Oct 2024 14:32:34 +0300 Subject: [PATCH 202/334] chore: stylish fixes --- src/core/component/decorators/watch/decorator.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/core/component/decorators/watch/decorator.ts b/src/core/component/decorators/watch/decorator.ts index 554f303df8..2003963b43 100644 --- a/src/core/component/decorators/watch/decorator.ts +++ b/src/core/component/decorators/watch/decorator.ts @@ -10,13 +10,7 @@ import { createComponentDecorator, normalizeFunctionalParams } from 'core/compon import type { PartDecorator } from 'core/component/decorators/interface'; -import type { - - DecoratorWatcher, - DecoratorMethodWatcher, - DecoratorFieldWatcher - -} from 'core/component/decorators/watch/interface'; +import type { DecoratorFieldWatcher, DecoratorMethodWatcher } from 'core/component/decorators/watch/interface'; /** * Attaches a watcher of a component property/event to a component method or property. @@ -128,8 +122,8 @@ import type { * * @param watcher - parameters for observation */ -export function watch(watcher: DecoratorWatcher): PartDecorator { - return createComponentDecorator(({meta}, key, desc?) => { +export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): PartDecorator { + return createComponentDecorator(({meta}, key, desc) => { if (desc == null) { decorateField(); From 41b593c7dc35a8cd8cdbe784f30a702c43b3210a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 8 Oct 2024 14:50:19 +0300 Subject: [PATCH 203/334] chore: better typings --- src/core/component/decorators/watch/decorator.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/component/decorators/watch/decorator.ts b/src/core/component/decorators/watch/decorator.ts index 2003963b43..6e6cd6793e 100644 --- a/src/core/component/decorators/watch/decorator.ts +++ b/src/core/component/decorators/watch/decorator.ts @@ -8,6 +8,8 @@ import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; +import type { ComponentProp, ComponentField, ComponentMethod } from 'core/component/interface'; + import type { PartDecorator } from 'core/component/decorators/interface'; import type { DecoratorFieldWatcher, DecoratorMethodWatcher } from 'core/component/decorators/watch/interface'; @@ -134,7 +136,7 @@ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): function decorateMethod() { const methodWatchers = Array.toArray(watcher); - const method = meta.methods[key] ?? { + const method: ComponentMethod = meta.methods[key] ?? { src: meta.componentName, fn: Object.throw, watchers: {} @@ -169,10 +171,8 @@ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): store = meta.systemFields; } - const field = store[key] ?? { + const field: ComponentProp | ComponentField = store[key] ?? { src: meta.componentName, - handler: Object.throw, - watchers: new Map(), meta: {} }; From 43ff8c4c9e5c93006fedae9a95be35533e123f14 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 8 Oct 2024 16:00:29 +0300 Subject: [PATCH 204/334] refactor: extract @field/@system from factory --- .../component/decorators/field/decorator.ts | 38 +++ src/core/component/decorators/field/index.ts | 32 +-- .../component/decorators/field/interface.ts | 9 + .../component/decorators/system/README.md | 2 +- .../component/decorators/system/decorator.ts | 143 ++++++++++ src/core/component/decorators/system/index.ts | 32 +-- .../component/decorators/system/interface.ts | 259 ++++++++++++++++++ src/core/component/interface/watch.ts | 19 +- src/core/component/meta/interface/meta.ts | 3 +- src/core/component/meta/interface/types.ts | 13 +- 10 files changed, 473 insertions(+), 77 deletions(-) create mode 100644 src/core/component/decorators/field/decorator.ts create mode 100644 src/core/component/decorators/field/interface.ts create mode 100644 src/core/component/decorators/system/decorator.ts create mode 100644 src/core/component/decorators/system/interface.ts diff --git a/src/core/component/decorators/field/decorator.ts b/src/core/component/decorators/field/decorator.ts new file mode 100644 index 0000000000..d98f3802a5 --- /dev/null +++ b/src/core/component/decorators/field/decorator.ts @@ -0,0 +1,38 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import type { PartDecorator } from 'core/component/decorators/interface'; + +import { system } from 'core/component/decorators/system'; +import type { InitFieldFn, DecoratorField } from 'core/component/decorators/field/interface'; + +/** + * Marks a class property as a component field. + * In non-functional components, field property mutations typically cause the component to re-render. + * + * @param [initOrParams] - a function to initialize the field value or an object with field parameters + * + * @decorator + * + * @example + * ```typescript + * import iBlock, { component, field } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @field() + * bla: number = 0; + * + * @field(() => Math.random()) + * baz?: number; + * } + * ``` + */ +export function field(initOrParams?: InitFieldFn | DecoratorField): PartDecorator { + return system(initOrParams, 'fields'); +} diff --git a/src/core/component/decorators/field/index.ts b/src/core/component/decorators/field/index.ts index 1f40a3376b..5db0cd9f7a 100644 --- a/src/core/component/decorators/field/index.ts +++ b/src/core/component/decorators/field/index.ts @@ -11,33 +11,5 @@ * @packageDocumentation */ -import { paramsFactory } from 'core/component/decorators/factory'; -import type { InitFieldFn, DecoratorField } from 'core/component/decorators/interface'; - -/** - * Marks a class property as a component field. - * In non-functional components, field property mutations typically cause the component to re-render. - * - * @decorator - * - * @example - * ```typescript - * import iBlock, { component, field } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @field() - * bla: number = 0; - * - * @field(() => Math.random()) - * baz?: number; - * } - * ``` - */ -export const field = paramsFactory('fields', (p) => { - if (Object.isFunction(p)) { - return {init: p}; - } - - return p; -}); +export * from 'core/component/decorators/field/decorator'; +export * from 'core/component/decorators/field/interface'; diff --git a/src/core/component/decorators/field/interface.ts b/src/core/component/decorators/field/interface.ts new file mode 100644 index 0000000000..328e4a8aab --- /dev/null +++ b/src/core/component/decorators/field/interface.ts @@ -0,0 +1,9 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +export * from 'core/component/decorators/system'; diff --git a/src/core/component/decorators/system/README.md b/src/core/component/decorators/system/README.md index 23bfd6699a..3260b25334 100644 --- a/src/core/component/decorators/system/README.md +++ b/src/core/component/decorators/system/README.md @@ -1,7 +1,7 @@ # core/component/decorators/system The decorator marks a class property as a system field. -System property mutations never cause components to re-render. +Mutations to a system field never cause components to re-render. ```typescript import iBlock, { component, system } from 'components/super/i-block/i-block'; diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts new file mode 100644 index 0000000000..7a468b60c5 --- /dev/null +++ b/src/core/component/decorators/system/decorator.ts @@ -0,0 +1,143 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { defProp } from 'core/const/props'; +import { isStore } from 'core/component/reflect'; + +import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; + +import type { ComponentField } from 'core/component/interface'; + +import type { PartDecorator } from 'core/component/decorators/interface'; + +import type { InitFieldFn, DecoratorSystem, DecoratorField } from 'core/component/decorators/system/interface'; + +/** + * Marks a class property as a system field. + * Mutations to a system field never cause components to re-render. + * + * @param [initOrParams] - a function to initialize the field value or an object with field parameters + * @param [type] - the type of the registered field: `systemFields` or `fields` + * + * @decorator + * + * @example + * ```typescript + * import iBlock, { component, system } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @system() + * bla: number = 0; + * + * @system(() => Math.random()) + * baz!: number; + * } + * ``` + */ +export function system(initOrParams?: InitFieldFn | DecoratorSystem, type?: 'systemFields'): PartDecorator; + +/** + * Marks a class property as a field. + * + * @param [initOrParams] - a function to initialize the field value or an object with field parameters + * @param [type] - the type of the registered field: `systemFields` or `fields` + */ +export function system(initOrParams: CanUndef, type: 'fields'): PartDecorator; + +export function system( + initOrParams?: InitFieldFn | DecoratorSystem | DecoratorField, + type: 'fields' | 'systemFields' = 'systemFields' +): PartDecorator { + return createComponentDecorator(({meta}, key) => { + const params = Object.isFunction(initOrParams) ? {init: initOrParams} : {...initOrParams}; + + delete meta.methods[key]; + delete meta.accessors[key]; + delete meta.computedFields[key]; + + const accessors = meta.accessors[key] != null ? + meta.accessors : + meta.computedFields; + + if (accessors[key] != null) { + Object.defineProperty(meta.constructor.prototype, key, defProp); + delete accessors[key]; + } + + // Handling the situation when a field changes type during inheritance, + // for example, it was a @prop in the parent component and became a @system + for (const key of ['props', type === 'fields' ? 'systemFields' : 'fields']) { + const cluster = meta[key]; + + if (key in cluster) { + const field: ComponentField = {...cluster[key]}; + + // Do not inherit the `functional` option in this case + delete field.functional; + + if (key === 'props') { + if (Object.isFunction(field.default)) { + field.init = field.default; + delete field.default; + } + } + + meta[type][key] = field; + delete cluster[key]; + + break; + } + } + + const field: ComponentField = meta[type][key] ?? { + src: meta.componentName, + meta: {} + }; + + let {watchers, after} = field; + + if (params.after != null) { + after = new Set(Array.toArray(params.after)); + } + + if (params.watch != null) { + watchers ??= new Map(); + + for (const fieldWatcher of Array.toArray(params.watch)) { + if (Object.isPlainObject(fieldWatcher)) { + // FIXME: remove Object.cast + watchers.set(fieldWatcher.handler, Object.cast(normalizeFunctionalParams({...fieldWatcher}, meta))); + + } else { + // FIXME: remove Object.cast + watchers.set(fieldWatcher, Object.cast(normalizeFunctionalParams({handler: fieldWatcher}, meta))); + } + } + } + + meta[type][key] = normalizeFunctionalParams({ + ...field, + ...params, + + after, + watchers, + + meta: { + ...field.meta, + ...params.meta + } + }, meta); + + if (isStore.test(key)) { + const tiedWith = isStore.replace(key); + meta.tiedFields[key] = tiedWith; + meta.tiedFields[tiedWith] = key; + } + }); +} diff --git a/src/core/component/decorators/system/index.ts b/src/core/component/decorators/system/index.ts index 7a77aa7bfc..07eb63a339 100644 --- a/src/core/component/decorators/system/index.ts +++ b/src/core/component/decorators/system/index.ts @@ -11,33 +11,5 @@ * @packageDocumentation */ -import { paramsFactory } from 'core/component/decorators/factory'; -import type { InitFieldFn, DecoratorSystem } from 'core/component/decorators/interface'; - -/** - * Marks a class property as a system field. - * System field mutations never cause components to re-render. - * - * @decorator - * - * @example - * ```typescript - * import iBlock, { component, system } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @system() - * bla: number = 0; - * - * @system(() => Math.random()) - * baz!: number; - * } - * ``` - */ -export const system = paramsFactory('systemFields', (p) => { - if (Object.isFunction(p)) { - return {init: p}; - } - - return p; -}); +export * from 'core/component/decorators/system/decorator'; +export * from 'core/component/decorators/system/interface'; diff --git a/src/core/component/decorators/system/interface.ts b/src/core/component/decorators/system/interface.ts new file mode 100644 index 0000000000..a3465dbb53 --- /dev/null +++ b/src/core/component/decorators/system/interface.ts @@ -0,0 +1,259 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import type { ComponentInterface } from 'core/component/interface'; +import type { DecoratorFieldWatcher } from 'core/component/decorators/watch'; +import type { DecoratorFunctionalOptions } from 'core/component/decorators/interface'; + +export interface DecoratorSystem< + Ctx extends ComponentInterface = ComponentInterface, + A = unknown, + B = A +> extends DecoratorFunctionalOptions { + /** + * Marks the field as unique for each component instance. + * Also, the parameter can take a function that returns a boolean value. + * If this value is true, then the parameter is considered unique. + * + * Please note that the "external" code must provide the uniqueness guarantee + * because V4Fire does not perform special checks for uniqueness. + * + * @default `false` + */ + unique?: boolean | UniqueFieldFn; + + /** + * This option allows you to set the default value of the field. + * But using it, as a rule, is not explicitly required, since the default value can be passed through + * the native syntax of class properties. + * + * Note that if the default value is set using class property syntax, then it is a prototype, not a real value. + * That is, when set to each new instance, it will be cloned using `Object.fastClone`. + * If this behavior does not suit you, then pass the value explicitly via `default` or using the `init` option and + * an initializer function. + * + * @example + * ```typescript + * import iBlock, { component, system } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @system() + * bla: number = 0; + * + * @field({default: 0}) + * bar!: number; + * + * // There will be a trouble here when cloning the value + * @field() + * body: Element = document.body; + * + * // All fine + * @field({default: document.body}) + * validBody!: Element; + * + * // All fine + * @field(() => document.body) + * validBody2!: Element; + * } + * ``` + */ + default?: unknown; + + /** + * A function to initialize the field value. + * The function takes as its first argument a reference to the component context. + * As the second argument, the function takes a reference to a dictionary with other fields of the same type that + * have already been initialized. + * + * @example + * ```typescript + * import iBlock, { component, system } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @system({init: Math.random}) + * hashCode!: number; + * + * @system((ctx, {hashCode}) => String(hashCode)) + * normalizedHashCode!: string; + * } + * ``` + */ + init?: InitFieldFn; + + /** + * A name or a list of names after which this property should be initialized. + * Keep in mind, you can only specify names that are of the same type as the current field (`@system` or `@field`). + * + * @example + * ```typescript + * import iBlock, { component, system } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @system(Math.random) + * hashCode!: number; + * + * @system({ + * after: 'hashCode', + * init: (ctx, {hashCode}) => String(hashCode) + * }) + * + * normalizedHashCode!: string; + * } + * ``` + */ + after?: CanArray; + + /** + * Indicates that property should be initialized before all non-atom properties. + * This option is necessary when you have a field that must be guaranteed to be initialized before other fields, + * and you don't want to use `after` everywhere. But you can still use `after` along with other atomic fields. + * + * @default `false` + * + * @example + * ```typescript + * import Async from 'core/async'; + * import iBlock, { component, system } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @system({atom: true, init: (ctx) => new Async(ctx)}) + * async!: Async; + * + * @system((ctx, data) => data.async.proxy(() => { /* ... *\/ })) + * handler!: Function; + * } + * ``` + */ + atom?: boolean; + + /** + * A watcher or a list of watchers for the current field. + * The watcher can be defined as a component method to invoke, callback function, or watch handle. + * + * The `core/watch` module is used to make objects watchable. + * Therefore, for more information, please refer to its documentation. + * + * @example + * ```typescript + * import iBlock, { component, system } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @system({watch: [ + * 'onIncrement', + * + * (ctx, val, oldVal, info) => + * console.log(val, oldVal, info), + * + * // Also, see core/object/watch + * { + * // If set to false, the handler invoked on the watcher event does not take any arguments from the event + * provideArgs: false, + * + * // How the event handler should be called: + * // + * // 1. `'post'` - the handler will be called on the next tick after the mutation and + * // guaranteed after updating all tied templates; + * // + * // 2. `'pre'` - the handler will be called on the next tick after the mutation and + * // guaranteed before updating all tied templates; + * // + * // 3. `'sync'` - the handler will be invoked immediately after each mutation. + * flush: 'sync', + * + * // Can define as a function too + * handler: 'onIncrement' + * } + * ]}) + * + * i: number = 0; + * + * onIncrement(val, oldVal, info) { + * console.log(val, oldVal, info); + * } + * } + * ``` + */ + watch?: DecoratorFieldWatcher; + + /** + * If set to false, the field can't be watched if created inside a functional component. + * This option is useful when you are writing a superclass or a smart component that can be created + * as regular or functional. + * + * @default `true` + */ + functionalWatching?: boolean; + + /** + * This option is only relevant for functional components. + * The fact is that when a component state changes, all its child functional components are recreated from scratch. + * But we need to restore the state of such components. By default, properties are simply copied from old instances to + * new ones, but sometimes this strategy does not suit us. This option helps here - it allows you to declare that + * a certain property should be mixed based on the old and new values. + * + * Set this property to true to enable the strategy of merging old and new values. + * Or specify a function that will perform the merge. This function takes contexts of the old and new components, + * the name of the field to restore, and optionally, a path to a property to which the given is bound. + * + * @default `false` + */ + merge?: MergeFieldFn | boolean; + + /** + * A dictionary with some extra information of the field. + * You can access this information using `meta.fields` or `meta.systemFields`. + * + * @example + * ```typescript + * import iBlock, { component, system } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @system({init: Math.random, meta: {debug: true}}) + * hashCode!: number; + * + * created() { + * // {debug: true}} + * console.log(this.meta.systemFields.hashCode.meta); + * } + * } + * ``` + */ + meta?: Dictionary; +} + +export interface DecoratorField< + Ctx extends ComponentInterface = ComponentInterface, + A = unknown, + B = A +> extends DecoratorSystem { + /** + * If set to true, property changes will cause the template to be guaranteed to be re-rendered. + * Be aware that enabling this property may result in redundant redrawing. + * + * @default `false` + */ + forceUpdate?: boolean; +} + +export interface InitFieldFn { + (ctx: Ctx['unsafe'], data: Dictionary): unknown; +} + +export interface MergeFieldFn { + (ctx: Ctx['unsafe'], oldCtx: Ctx['unsafe'], field: string, link?: string): unknown; +} + +export interface UniqueFieldFn { + (ctx: Ctx['unsafe'], oldCtx: Ctx['unsafe']): AnyToBoolean; +} diff --git a/src/core/component/interface/watch.ts b/src/core/component/interface/watch.ts index 96e7ad9549..218922e8c0 100644 --- a/src/core/component/interface/watch.ts +++ b/src/core/component/interface/watch.ts @@ -39,13 +39,14 @@ export interface WatchOptions extends RawWatchOptions { } export interface FieldWatcher< + Ctx extends ComponentInterface = ComponentInterface, A = unknown, B = A > extends WatchOptions { /** * This handler is called when a watcher event occurs */ - handler: WatchHandler; + handler: string | WatchHandler; /** * If set to false, the watcher will not be registered for functional components @@ -63,7 +64,7 @@ export interface FieldWatcher< } export interface WatchObject< - CTX extends ComponentInterface = ComponentInterface, + Ctx extends ComponentInterface = ComponentInterface, A = unknown, B = A > extends WatchOptions { @@ -133,7 +134,7 @@ export interface WatchObject< * } * ``` */ - wrapper?: WatchWrapper; + wrapper?: WatchWrapper; /** * The name of a component method that is registered as a handler for the watcher @@ -152,11 +153,11 @@ export interface WatchObject< * * @param ctx */ - shouldInit?(ctx: CTX): boolean; + shouldInit?(ctx: Ctx): boolean; } export interface MethodWatcher< - CTX extends ComponentInterface = ComponentInterface, + Ctx extends ComponentInterface = ComponentInterface, A = unknown, B = A > extends WatchOptions { @@ -190,7 +191,7 @@ export interface MethodWatcher< * * @param ctx */ - shouldInit?(ctx: CTX): boolean; + shouldInit?(ctx: Ctx): boolean; /** * An object with additional settings for the event emitter @@ -228,7 +229,7 @@ export interface MethodWatcher< * } * ``` */ - wrapper?: WatchWrapper; + wrapper?: WatchWrapper; } export type WatchPath = @@ -236,9 +237,9 @@ export type WatchPath = PropertyInfo | {ctx: object; path?: RawWatchPath}; -export interface RawWatchHandler { +export interface RawWatchHandler { (a: A, b?: B, params?: WatchHandlerParams): void; - (this: CTX, a: A, b?: B, params?: WatchHandlerParams): void; + (this: Ctx, a: A, b?: B, params?: WatchHandlerParams): void; } export interface WatchHandler { diff --git a/src/core/component/meta/interface/meta.ts b/src/core/component/meta/interface/meta.ts index 43cc1e1176..d3849a17b6 100644 --- a/src/core/component/meta/interface/meta.ts +++ b/src/core/component/meta/interface/meta.ts @@ -18,6 +18,7 @@ import type { ComponentProp, ComponentField, + ComponentSystemField, ComponentFieldInitializers, ComponentMethod, @@ -100,7 +101,7 @@ export interface ComponentMeta { * It's important to identify and distinguish these non-reactive fields from the reactive ones, * and to use them appropriately to optimize the performance of the component. */ - systemFields: Dictionary; + systemFields: Dictionary; /** * A sorted array of system fields and functions for their initialization on the component diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index d5196bb5a6..13a61fbd5f 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -23,7 +23,7 @@ export interface ComponentProp extends PropOptions { meta: Dictionary; } -export interface ComponentSystemField { +export interface ComponentSystemField { src: string; meta: Dictionary; @@ -31,18 +31,19 @@ export interface ComponentSystemField; default?: unknown; - unique?: boolean | UniqueFieldFn; + unique?: boolean | UniqueFieldFn; functional?: boolean; functionalWatching?: boolean; - init?: InitFieldFn; - merge?: MergeFieldFn | boolean; + init?: InitFieldFn; + merge?: MergeFieldFn | boolean; + + watchers?: Map; } -export interface ComponentField extends ComponentSystemField { +export interface ComponentField extends ComponentSystemField { forceUpdate?: boolean; - watchers?: Map; } export interface ComponentFieldInitializer { From 4ea39f68131bbcc552caf26eb641ce4fa27bd010 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 8 Oct 2024 17:01:05 +0300 Subject: [PATCH 205/334] refactor: extract @prop from factory --- .../component/decorators/interface/index.ts | 1 - .../component/decorators/interface/method.ts | 59 --------- .../component/decorators/interface/watcher.ts | 78 ------------ .../component/decorators/prop/decorator.ts | 118 ++++++++++++++++++ src/core/component/decorators/prop/index.ts | 38 +----- .../{interface/prop.ts => prop/interface.ts} | 6 +- .../component/decorators/watch/interface.ts | 4 - src/core/component/meta/interface/types.ts | 2 +- 8 files changed, 124 insertions(+), 182 deletions(-) delete mode 100644 src/core/component/decorators/interface/method.ts delete mode 100644 src/core/component/decorators/interface/watcher.ts create mode 100644 src/core/component/decorators/prop/decorator.ts rename src/core/component/decorators/{interface/prop.ts => prop/interface.ts} (98%) diff --git a/src/core/component/decorators/interface/index.ts b/src/core/component/decorators/interface/index.ts index f5bb1431a3..37468b35ba 100644 --- a/src/core/component/decorators/interface/index.ts +++ b/src/core/component/decorators/interface/index.ts @@ -8,7 +8,6 @@ export * from 'core/component/decorators/interface/watcher'; export * from 'core/component/decorators/interface/hook'; -export * from 'core/component/decorators/interface/prop'; export * from 'core/component/decorators/interface/field'; export * from 'core/component/decorators/interface/method'; export * from 'core/component/decorators/interface/accessor'; diff --git a/src/core/component/decorators/interface/method.ts b/src/core/component/decorators/interface/method.ts deleted file mode 100644 index 1b58419ee6..0000000000 --- a/src/core/component/decorators/interface/method.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import type { ComponentInterface, MethodWatcher } from 'core/component/interface'; -import type { DecoratorHook } from 'core/component/decorators/interface/hook'; - -export interface DecoratorMethod { - /** - * A path specifies the property to watch, or you can provide a list of such paths. - * Whenever a mutation occurs in any one of the specified properties, this method will be invoked. - * You can also set additional parameters for watching. - * - * The `core/watch` module is used to make objects watchable. - * Therefore, for more information, please refer to its documentation. - * - * @example - * ```typescript - * import iBlock, { component, field, system, watch } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @system() - * i: number = 0; - * - * @field() - * opts: Dictionary = {a: {b: 1}}; - * - * @watch(['i', {path: 'opts.a.b', flush: 'sync'}]) - * onIncrement(val, oldVal, info) { - * console.log(val, oldVal, info); - * } - * } - * ``` - */ - watch?: DecoratorMethodWatcher; - - /** - * Watch parameters that are applied for all watchers. - * - * The `core/watch` module is used to make objects watchable. - * Therefore, for more information, please refer to its documentation. - */ - watchParams?: MethodWatcher; - - /** - * A component lifecycle hook or a list of such hooks on which this method should be called - */ - hook?: DecoratorHook; -} - -export type DecoratorMethodWatcher = - string | - MethodWatcher | - Array>; diff --git a/src/core/component/decorators/interface/watcher.ts b/src/core/component/decorators/interface/watcher.ts deleted file mode 100644 index 3431f708cf..0000000000 --- a/src/core/component/decorators/interface/watcher.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import type { ComponentInterface, WatchOptions, WatchHandlerParams } from 'core/component/interface'; - -export interface DecoratorFieldWatcherObject< - CTX extends ComponentInterface = ComponentInterface, - A = unknown, - B = A -> extends WatchOptions { - /** - * A function (or the name of a component method) that gets triggered on watcher events - * - * @example - * ```typescript - * import iBlock, { component, field, watch } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @watch({handler: 'onIncrement'}) - * @field() - * i: number = 0; - * - * onIncrement(val, oldVal, info) { - * console.log(val, oldVal, info); - * } - * } - * ``` - */ - handler: string | DecoratorWatchHandler; - - /** - * If set to false, a handler that gets invoked due to a watcher event won't take any arguments from the event - * - * @default `true` - * - * @example - * ```typescript - * import iBlock, { component, field } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @field({watch: {handler: 'onIncrement', provideArgs: false}}) - * i: number = 0; - * - * onIncrement(val) { - * console.log(val === undefined); - * } - * } - * ``` - */ - provideArgs?: boolean; - - /** - * A function to determine whether a watcher should be initialized or not. - * If the function returns false, the watcher will not be initialized. - * Useful for precise component optimizations. - * - * @param ctx - */ - shouldInit?(ctx: CTX): boolean; -} - -export interface DecoratorWatchHandler { - (ctx: CTX['unsafe'], a: A, b: B, params?: WatchHandlerParams): unknown; - (ctx: CTX['unsafe'], ...args: A[]): unknown; -} - -export type DecoratorFieldWatcher = - string | - DecoratorFieldWatcherObject | - DecoratorWatchHandler | - Array | DecoratorWatchHandler>; diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts new file mode 100644 index 0000000000..71bafac039 --- /dev/null +++ b/src/core/component/decorators/prop/decorator.ts @@ -0,0 +1,118 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { defProp } from 'core/const/props'; + +import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; + +import type { ComponentProp, ComponentField } from 'core/component/interface'; + +import type { PartDecorator } from 'core/component/decorators/interface'; + +import type { DecoratorProp, PropType } from 'core/component/decorators/prop/interface'; + +/** + * Marks a class property as a component prop + * + * @param [typeOrParams] - a constructor of the prop type or an object with prop parameters + * + * @decorator + * + * @example + * ```typescript + * import iBlock, { component, prop } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @prop(Number) + * bla: number = 0; + * + * @prop({type: Number, required: false}) + * baz?: number; + * + * @prop({type: Number, default: () => Math.random()}) + * bar!: number; + * } + * ``` + */ +export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { + return createComponentDecorator(({meta}, key) => { + const params: DecoratorProp = Object.isFunction(typeOrParams) || Object.isArray(typeOrParams) ? + {type: typeOrParams, forceUpdate: true} : + {forceUpdate: true, ...typeOrParams}; + + delete meta.methods[key]; + delete meta.accessors[key]; + delete meta.computedFields[key]; + + const accessors = meta.accessors[key] != null ? + meta.accessors : + meta.computedFields; + + if (accessors[key] != null) { + Object.defineProperty(meta.constructor.prototype, key, defProp); + delete accessors[key]; + } + + // Handling the situation when a field changes type during inheritance, + // for example, it was a @system in the parent component and became a @prop + for (const key of ['fields', 'systemFields']) { + const cluster = meta[key]; + + if (key in cluster) { + const field: ComponentField = {...cluster[key]}; + + // Do not inherit the `functional` option in this case + delete field.functional; + + // The option `init` cannot be converted to `default` + delete field.init; + + meta.props[key] = {...field, forceUpdate: true}; + + delete cluster[key]; + + break; + } + } + + const prop: ComponentProp = meta.props[key] ?? { + forceUpdate: true, + meta: {} + }; + + let {watchers} = prop; + + if (params.watch != null) { + watchers ??= new Map(); + + for (const fieldWatcher of Array.toArray(params.watch)) { + if (Object.isPlainObject(fieldWatcher)) { + // FIXME: remove Object.cast + watchers.set(fieldWatcher.handler, Object.cast(normalizeFunctionalParams({...fieldWatcher}, meta))); + + } else { + // FIXME: remove Object.cast + watchers.set(fieldWatcher, Object.cast(normalizeFunctionalParams({handler: fieldWatcher}, meta))); + } + } + } + + meta.props[key] = normalizeFunctionalParams({ + ...prop, + ...params, + + watchers, + + meta: { + ...prop.meta, + ...params.meta + } + }, meta); + }); +} diff --git a/src/core/component/decorators/prop/index.ts b/src/core/component/decorators/prop/index.ts index e2c9289ba3..45f45d2b72 100644 --- a/src/core/component/decorators/prop/index.ts +++ b/src/core/component/decorators/prop/index.ts @@ -11,45 +11,11 @@ * @packageDocumentation */ -import { paramsFactory } from 'core/component/decorators/factory'; -import type { DecoratorProp } from 'core/component/decorators/interface'; - //#if runtime has dummyComponents import('core/component/decorators/prop/test/b-effect-prop-wrapper-dummy'); import('core/component/decorators/prop/test/b-effect-prop-dummy'); import('core/component/decorators/prop/test/b-non-effect-prop-dummy'); //#endif -/** - * Marks a class property as a component prop - * - * @decorator - * - * @example - * ```typescript - * import iBlock, { component, prop } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @prop(Number) - * bla: number = 0; - * - * @prop({type: Number, required: false}) - * baz?: number; - * - * @prop({type: Number, default: () => Math.random()}) - * bar!: number; - * } - * ``` - */ -export const prop = paramsFactory< - CanArray | - ObjectConstructor | - DecoratorProp ->('props', (p) => { - if (Object.isFunction(p) || Object.isArray(p)) { - return {type: p, forceUpdate: true}; - } - - return {forceUpdate: true, ...p}; -}); +export * from 'core/component/decorators/prop/decorator'; +export * from 'core/component/decorators/prop/interface'; diff --git a/src/core/component/decorators/interface/prop.ts b/src/core/component/decorators/prop/interface.ts similarity index 98% rename from src/core/component/decorators/interface/prop.ts rename to src/core/component/decorators/prop/interface.ts index 6f89b33c13..b57ac8dec8 100644 --- a/src/core/component/decorators/interface/prop.ts +++ b/src/core/component/decorators/prop/interface.ts @@ -7,7 +7,7 @@ */ import type { ComponentInterface } from 'core/component/interface'; -import type { DecoratorFieldWatcher } from 'core/component/decorators/interface/watcher'; +import type { DecoratorFieldWatcher } from 'core/component/decorators/watch'; /** * Options of a component prop @@ -214,7 +214,7 @@ export interface DecoratorProp< export type Prop = {(): T} | - {new(...args: any[]): T & object} | - {new(...args: string[]): Function}; + {new (...args: any[]): T & object} | + {new (...args: string[]): Function}; export type PropType = CanArray>; diff --git a/src/core/component/decorators/watch/interface.ts b/src/core/component/decorators/watch/interface.ts index c16465f9cd..2705f0c6a5 100644 --- a/src/core/component/decorators/watch/interface.ts +++ b/src/core/component/decorators/watch/interface.ts @@ -8,10 +8,6 @@ import type { ComponentInterface, MethodWatcher, WatchHandlerParams, WatchOptions } from 'core/component/interface'; -export type DecoratorWatcher = - DecoratorMethodWatcher | - DecoratorFieldWatcher; - export type DecoratorMethodWatcher = string | MethodWatcher & {path: string} | diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index 13a61fbd5f..ccea65acd8 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -17,7 +17,7 @@ export interface ComponentProp extends PropOptions { forceUpdate: boolean; forceDefault?: boolean; - watchers?: Map; + watchers?: Map; default?: unknown; meta: Dictionary; From 3cbcd00c794b70f9ac8146af31cfd7d12b10fbca Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 8 Oct 2024 17:15:17 +0300 Subject: [PATCH 206/334] chore: stylish fixes --- src/core/component/decorators/field/decorator.ts | 3 +-- src/core/component/decorators/prop/decorator.ts | 3 +-- src/core/component/decorators/system/decorator.ts | 4 ++-- src/core/component/decorators/watch/decorator.ts | 7 +++++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/core/component/decorators/field/decorator.ts b/src/core/component/decorators/field/decorator.ts index d98f3802a5..d33a126513 100644 --- a/src/core/component/decorators/field/decorator.ts +++ b/src/core/component/decorators/field/decorator.ts @@ -15,9 +15,8 @@ import type { InitFieldFn, DecoratorField } from 'core/component/decorators/fiel * Marks a class property as a component field. * In non-functional components, field property mutations typically cause the component to re-render. * - * @param [initOrParams] - a function to initialize the field value or an object with field parameters - * * @decorator + * @param [initOrParams] - a function to initialize the field value or an object with field parameters * * @example * ```typescript diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 71bafac039..003f7f3157 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -19,9 +19,8 @@ import type { DecoratorProp, PropType } from 'core/component/decorators/prop/int /** * Marks a class property as a component prop * - * @param [typeOrParams] - a constructor of the prop type or an object with prop parameters - * * @decorator + * @param [typeOrParams] - a constructor of the prop type or an object with prop parameters * * @example * ```typescript diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index 7a468b60c5..01f4b02eb2 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -21,11 +21,11 @@ import type { InitFieldFn, DecoratorSystem, DecoratorField } from 'core/componen * Marks a class property as a system field. * Mutations to a system field never cause components to re-render. * + * @decorator + * * @param [initOrParams] - a function to initialize the field value or an object with field parameters * @param [type] - the type of the registered field: `systemFields` or `fields` * - * @decorator - * * @example * ```typescript * import iBlock, { component, system } from 'components/super/i-block/i-block'; diff --git a/src/core/component/decorators/watch/decorator.ts b/src/core/component/decorators/watch/decorator.ts index 6e6cd6793e..2f43646694 100644 --- a/src/core/component/decorators/watch/decorator.ts +++ b/src/core/component/decorators/watch/decorator.ts @@ -22,6 +22,11 @@ import type { DecoratorFieldWatcher, DecoratorMethodWatcher } from 'core/compone * If the object being watched isn't primitive, the old value will be cloned from the original old value. * This helps avoid issues that may arise from having two references to the same object. * + * @decorator + * @param watcher - parameters for observation + * + * @example + * * ```typescript * import iBlock, { component, field, watch } from 'components/super/i-block/i-block'; * @@ -121,8 +126,6 @@ import type { DecoratorFieldWatcher, DecoratorMethodWatcher } from 'core/compone * } * } * ``` - * - * @param watcher - parameters for observation */ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): PartDecorator { return createComponentDecorator(({meta}, key, desc) => { From 4180a150efc6a3078c8fb1edf8952cda86d98217 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 8 Oct 2024 17:19:33 +0300 Subject: [PATCH 207/334] refactor: extract @hook from factory --- src/core/component/decorators/hook/README.md | 2 +- .../component/decorators/hook/decorator.ts | 73 +++++++++++++++++++ src/core/component/decorators/hook/index.ts | 25 +------ .../{interface/hook.ts => hook/interface.ts} | 3 +- .../component/decorators/interface/index.ts | 3 - 5 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 src/core/component/decorators/hook/decorator.ts rename src/core/component/decorators/{interface/hook.ts => hook/interface.ts} (88%) diff --git a/src/core/component/decorators/hook/README.md b/src/core/component/decorators/hook/README.md index 561073c548..52379c2fe5 100644 --- a/src/core/component/decorators/hook/README.md +++ b/src/core/component/decorators/hook/README.md @@ -243,7 +243,7 @@ then the rest will wait for its resolving to preserve the initialization order. ### [after] -A method name or a list of names after which this handler should be invoked on a registered hook event. +A method name or a list of method names after which this handler should be invoked during a registered hook event. ```typescript import iBlock, { component, hook } from 'components/super/i-block/i-block'; diff --git a/src/core/component/decorators/hook/decorator.ts b/src/core/component/decorators/hook/decorator.ts new file mode 100644 index 0000000000..fc3ecaaa7c --- /dev/null +++ b/src/core/component/decorators/hook/decorator.ts @@ -0,0 +1,73 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; + +import type { ComponentMethod } from 'core/component/interface'; + +import type { PartDecorator } from 'core/component/decorators/interface'; + +import type { DecoratorHook } from 'core/component/decorators/hook/interface'; + +/** + * Attaches a hook listener to a component method. + * This means that when the component switches to the specified hook(s), the method will be called. + * + * @decorator + * @param [hook] - the hook name, an array of hooks, or an object with hook parameters + * + * @example + * ```typescript + * import iBlock, { component, hook } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @hook('mounted') + * onMounted() { + * + * } + * } + * ``` + */ +export function hook(hook: DecoratorHook): PartDecorator { + return createComponentDecorator(({meta}, key, desc) => { + if (desc == null) { + return; + } + + const methodHooks = Array.toArray(hook); + + const method: ComponentMethod = meta.methods[key] ?? { + src: meta.componentName, + fn: Object.throw, + hooks: {} + }; + + const {hooks = {}} = method; + + for (const hook of methodHooks) { + if (Object.isSimpleObject(hook)) { + const + hookName = Object.keys(hook)[0], + hookParams = hook[hookName]; + + hooks[hookName] = normalizeFunctionalParams({ + ...hookParams, + name: key, + hook: hookName, + after: hookParams.after != null ? new Set(Array.toArray(hookParams.after)) : undefined + }, meta); + + } else { + hooks[hook] = normalizeFunctionalParams({name: key, hook}, meta); + } + } + + meta.methods[key] = normalizeFunctionalParams({...method}, meta); + }); +} diff --git a/src/core/component/decorators/hook/index.ts b/src/core/component/decorators/hook/index.ts index 216689bf71..754593fa2b 100644 --- a/src/core/component/decorators/hook/index.ts +++ b/src/core/component/decorators/hook/index.ts @@ -11,26 +11,5 @@ * @packageDocumentation */ -import { paramsFactory } from 'core/component/decorators/factory'; -import type { DecoratorHook } from 'core/component/decorators/interface'; - -/** - * Attaches a hook listener to a component method. - * This means that when the component switches to the specified hook(s), the method will be called. - * - * @decorator - * - * @example - * ```typescript - * import iBlock, { component, hook } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @hook('mounted') - * onMounted() { - * - * } - * } - * ``` - */ -export const hook = paramsFactory(null, (hook) => ({hook})); +export * from 'core/component/decorators/hook/decorator'; +export * from 'core/component/decorators/hook/interface'; diff --git a/src/core/component/decorators/interface/hook.ts b/src/core/component/decorators/hook/interface.ts similarity index 88% rename from src/core/component/decorators/interface/hook.ts rename to src/core/component/decorators/hook/interface.ts index 3000cf94cf..5f2898c57b 100644 --- a/src/core/component/decorators/interface/hook.ts +++ b/src/core/component/decorators/hook/interface.ts @@ -16,7 +16,8 @@ export type DecoratorHook = export type DecoratorHookOptions = { [hook in Hook]?: DecoratorFunctionalOptions & { /** - * A method name or a list of names after which this handler should be invoked on a registered hook event + * A method name or a list of method names after which + * this handler should be invoked during a registered hook event * * @example * ```typescript diff --git a/src/core/component/decorators/interface/index.ts b/src/core/component/decorators/interface/index.ts index 37468b35ba..68c03af746 100644 --- a/src/core/component/decorators/interface/index.ts +++ b/src/core/component/decorators/interface/index.ts @@ -6,9 +6,6 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -export * from 'core/component/decorators/interface/watcher'; -export * from 'core/component/decorators/interface/hook'; export * from 'core/component/decorators/interface/field'; -export * from 'core/component/decorators/interface/method'; export * from 'core/component/decorators/interface/accessor'; export * from 'core/component/decorators/interface/types'; From cee9cfdbfd8b0ca0da99c95e4d0341a168818611 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 8 Oct 2024 17:51:07 +0300 Subject: [PATCH 208/334] refactor: extract @computed from factory --- .../decorators/computed/decorator.ts | 89 ++++++ .../component/decorators/computed/index.ts | 24 +- .../accessor.ts => computed/interface.ts} | 4 +- src/core/component/decorators/factory.ts | 299 ------------------ src/core/component/decorators/index.ts | 1 - .../{interface/types.ts => interface.ts} | 0 .../component/decorators/interface/field.ts | 259 --------------- .../component/decorators/interface/index.ts | 11 - src/core/component/meta/interface/meta.ts | 4 +- src/core/component/meta/interface/types.ts | 5 +- 10 files changed, 96 insertions(+), 600 deletions(-) create mode 100644 src/core/component/decorators/computed/decorator.ts rename src/core/component/decorators/{interface/accessor.ts => computed/interface.ts} (97%) delete mode 100644 src/core/component/decorators/factory.ts rename src/core/component/decorators/{interface/types.ts => interface.ts} (100%) delete mode 100644 src/core/component/decorators/interface/field.ts delete mode 100644 src/core/component/decorators/interface/index.ts diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts new file mode 100644 index 0000000000..487c18ca40 --- /dev/null +++ b/src/core/component/decorators/computed/decorator.ts @@ -0,0 +1,89 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; + +import type { ComponentAccessor } from 'core/component/interface'; + +import type { PartDecorator } from 'core/component/decorators/interface'; + +import type { DecoratorComputed } from 'core/component/decorators/computed/interface'; + +/** + * Assigns meta-information to a computed field or an accessor within a component + * + * @decorator + * @param [params] - an object with accessor parameters + * + * @example + * ```typescript + * import iBlock, { component, computed } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @computed({cache: true}) + * get hashCode(): number { + * return Math.random(); + * } + * } + * ``` + */ +export function computed(params?: DecoratorComputed): PartDecorator { + return createComponentDecorator(({meta}, key, desc) => { + if (desc == null) { + return; + } + + params = {...params}; + + delete meta.accessors[key]; + delete meta.computedFields[key]; + + let type: 'accessors' | 'computedFields' = 'accessors'; + + if ( + params.cache === true || + params.cache === 'auto' || + params.cache === 'forever' || + params.cache !== false && (Object.isArray(params.dependencies) || key in meta.computedFields) + ) { + type = 'computedFields'; + } + + const accessor: ComponentAccessor = meta[type][key] ?? { + src: meta.componentName, + cache: false + }; + + const needOverrideComputed = type === 'accessors' ? + key in meta.computedFields : + !('cache' in params) && key in meta.accessors; + + if (needOverrideComputed) { + const computed = meta.computedFields[key]; + + meta[type][key] = normalizeFunctionalParams({ + ...computed, + ...params, + src: computed?.src ?? accessor.src, + cache: false + }, meta); + + } else { + meta[type][key] = normalizeFunctionalParams({ + ...accessor, + ...params, + cache: type === 'computedFields' ? params.cache ?? true : false + }, meta); + } + + if (params.dependencies != null && params.dependencies.length > 0) { + meta.watchDependencies.set(key, params.dependencies); + } + }); +} diff --git a/src/core/component/decorators/computed/index.ts b/src/core/component/decorators/computed/index.ts index 8c5350e17e..6cdb4c08d7 100644 --- a/src/core/component/decorators/computed/index.ts +++ b/src/core/component/decorators/computed/index.ts @@ -11,25 +11,5 @@ * @packageDocumentation */ -import { paramsFactory } from 'core/component/decorators/factory'; -import type { DecoratorComponentAccessor } from 'core/component/decorators/interface'; - -/** - * Assigns meta-information to a computed field or an accessor within a component - * - * @decorator - * - * @example - * ```typescript - * import iBlock, { component, computed } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @computed({cache: true}) - * get hashCode(): number { - * return Math.random(); - * } - * } - * ``` - */ -export const computed = paramsFactory(null); +export * from 'core/component/decorators/computed/decorator'; +export * from 'core/component/decorators/computed/interface'; diff --git a/src/core/component/decorators/interface/accessor.ts b/src/core/component/decorators/computed/interface.ts similarity index 97% rename from src/core/component/decorators/interface/accessor.ts rename to src/core/component/decorators/computed/interface.ts index 0d332d59c2..0fca9ad149 100644 --- a/src/core/component/decorators/interface/accessor.ts +++ b/src/core/component/decorators/computed/interface.ts @@ -7,9 +7,9 @@ */ import type { WatchPath, ComponentAccessorCacheType } from 'core/component/interface'; -import type { DecoratorFunctionalOptions } from 'core/component/decorators/interface/types'; +import type { DecoratorFunctionalOptions } from 'core/component/decorators/interface'; -export interface DecoratorComponentAccessor extends DecoratorFunctionalOptions { +export interface DecoratorComputed extends DecoratorFunctionalOptions { /** * If set to true, the accessor value will be cached after the first touch. * diff --git a/src/core/component/decorators/factory.ts b/src/core/component/decorators/factory.ts deleted file mode 100644 index f688ea50e3..0000000000 --- a/src/core/component/decorators/factory.ts +++ /dev/null @@ -1,299 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import { defProp } from 'core/const/props'; - -import { isStore } from 'core/component/reflect'; -import { initEmitter } from 'core/component/event'; - -import { componentDecoratedKeys } from 'core/component/const'; -import { invertedFieldMap, tiedFieldMap } from 'core/component/decorators/const'; - -import type { ComponentMeta, ComponentProp, ComponentField } from 'core/component/interface'; - -import type { - - DecoratorFunctionalOptions, - ParamsFactoryTransformer, - FactoryTransformer - -} from 'core/component/decorators/interface'; - -/** - * Factory for creating component property decorators - * - * @param cluster - the property cluster to decorate, like `fields` or `systemFields` - * @param [transformer] - a transformer for the passed decorator parameters - */ -export function paramsFactory( - cluster: Nullable, - transformer?: ParamsFactoryTransformer -): FactoryTransformer { - return (params: Dictionary = {}) => (_: object, key: string, desc?: PropertyDescriptor) => { - initEmitter.once('bindConstructor', (componentName, regEvent) => { - const decoratedKeys = componentDecoratedKeys[componentName] ?? new Set(); - componentDecoratedKeys[componentName] = decoratedKeys; - - decoratedKeys.add(key); - initEmitter.once(regEvent, decorate); - }); - - function decorate({meta}: {meta: ComponentMeta}): void { - delete meta.tiedFields[key]; - - let p = params; - - if (desc != null) { - decorateMethodOrAccessor(); - - } else { - decorateProperty(); - } - - function decorateMethodOrAccessor() { - if (desc == null) { - return; - } - - delete meta.props[key]; - delete meta.fields[key]; - delete meta.systemFields[key]; - - let metaKey: string; - - if (cluster != null) { - metaKey = cluster; - - } else if ('value' in desc) { - metaKey = 'methods'; - - } else if ( - p.cache === true || - p.cache === 'auto' || - p.cache === 'forever' || - p.cache !== false && (Object.isArray(p.dependencies) || key in meta.computedFields) - ) { - metaKey = 'computedFields'; - - } else { - metaKey = 'accessors'; - } - - if (transformer) { - p = transformer(p, metaKey); - } - - const - metaCluster = meta[metaKey], - info = metaCluster[key] ?? {src: meta.componentName}; - - if (metaKey === 'methods') { - decorateMethod(); - - } else { - decorateAccessor(); - } - - function decorateMethod() { - const name = key; - - let {watchers, hooks} = info; - - if (p.watch != null) { - watchers ??= {}; - - Array.toArray(p.watch).forEach((watcher) => { - if (Object.isPlainObject(watcher)) { - const path = String(watcher.path ?? watcher.field); - watchers[path] = wrapOpts({...p.watchParams, ...watcher, path}); - - } else { - watchers[watcher] = wrapOpts({...p.watchParams, path: watcher}); - } - }); - } - - if (p.hook != null) { - hooks ??= {}; - - Array.toArray(p.hook).forEach((hook) => { - if (Object.isSimpleObject(hook)) { - const - hookName = Object.keys(hook)[0], - hookInfo = hook[hookName]; - - hooks[hookName] = wrapOpts({ - ...hookInfo, - name, - hook: hookName, - after: hookInfo.after != null ? new Set([].concat(hookInfo.after)) : undefined - }); - - } else { - hooks[hook] = wrapOpts({name, hook}); - } - }); - } - - metaCluster[key] = wrapOpts({...info, ...p, watchers, hooks}); - } - - function decorateAccessor() { - delete meta.accessors[key]; - delete meta.computedFields[key]; - - const needOverrideComputed = metaKey === 'accessors' ? - key in meta.computedFields : - !('cache' in p) && key in meta.accessors; - - if (needOverrideComputed) { - metaCluster[key] = wrapOpts({...meta.computedFields[key], ...p, cache: false}); - - } else { - metaCluster[key] = wrapOpts({ - ...info, - ...p, - cache: metaKey === 'computedFields' ? p.cache ?? true : false - }); - } - - if (p.dependencies != null && p.dependencies.length > 0) { - meta.watchDependencies.set(key, p.dependencies); - } - } - } - - function decorateProperty() { - delete meta.methods[key]; - delete meta.accessors[key]; - delete meta.computedFields[key]; - - const accessors = meta.accessors[key] ? - meta.accessors : - meta.computedFields; - - if (accessors[key]) { - Object.defineProperty(meta.constructor.prototype, key, defProp); - delete accessors[key]; - } - - const - metaKey = cluster ?? (key in meta.props ? 'props' : 'fields'), - metaCluster: ComponentProp | ComponentField = meta[metaKey]; - - inheritFromParent(); - - if (transformer != null) { - p = transformer(p, metaKey); - } - - const info = metaCluster[key] ?? {src: meta.componentName}; - - let {watchers, after} = info; - - if (p.after != null) { - after = new Set([].concat(p.after)); - } - - if (p.watch != null) { - Array.toArray(p.watch).forEach((watcher) => { - watchers ??= new Map(); - - if (Object.isPlainObject(watcher)) { - watchers.set(watcher.handler ?? watcher.fn, wrapOpts({...watcher, handler: watcher.handler})); - - } else { - watchers.set(watcher, wrapOpts({handler: watcher})); - } - }); - } - - const desc = wrapOpts({ - ...info, - ...p, - - after, - watchers, - - meta: { - ...info.meta, - ...p.meta - } - }); - - metaCluster[key] = desc; - - if (metaKey === 'props' && desc.forceUpdate === false) { - // A special system property used to observe props with the option `forceUpdate: false`. - // This is because `forceUpdate: false` props are passed as attributes, - // i.e., they are accessible via `$attrs`. - // Moreover, all such attributes are readonly for the component. - // However, we need a system property that will be synchronized with this attribute - // and will update whenever this attribute is updated from the outside. - // Therefore, we introduce a special private system field formatted as `[[${fieldName}]]`. - meta.systemFields[`[[${key}]]`] = { - ...info, - watchers, - - meta: { - ...info.meta, - ...p.meta - } - }; - } - - if (tiedFieldMap[metaKey] != null && isStore.test(key)) { - const tiedWith = isStore.replace(key); - meta.tiedFields[key] = tiedWith; - meta.tiedFields[tiedWith] = key; - } - - function inheritFromParent() { - const invertedMetaKeys: CanUndef = invertedFieldMap[metaKey]; - - invertedMetaKeys?.some((invertedMetaKey) => { - const invertedMetaCluster = meta[invertedMetaKey]; - - if (key in invertedMetaCluster) { - const info = {...invertedMetaCluster[key]}; - delete info.functional; - - if (invertedMetaKey === 'prop') { - if (Object.isFunction(info.default)) { - (info).init = info.default; - delete info.default; - } - - } else if (metaKey === 'prop') { - delete (info).init; - } - - metaCluster[key] = info; - delete invertedMetaCluster[key]; - - return true; - } - - return false; - }); - } - } - - function wrapOpts(opts: T): T { - const p = meta.params; - - // eslint-disable-next-line eqeqeq - if (opts.functional === undefined && p.functional === null) { - opts.functional = false; - } - - return opts; - } - } - }; -} diff --git a/src/core/component/decorators/index.ts b/src/core/component/decorators/index.ts index c8f0e2d071..fff0b5a014 100644 --- a/src/core/component/decorators/index.ts +++ b/src/core/component/decorators/index.ts @@ -12,7 +12,6 @@ */ export * from 'core/component/decorators/component'; -export * from 'core/component/decorators/factory'; export * from 'core/component/decorators/prop'; export * from 'core/component/decorators/field'; diff --git a/src/core/component/decorators/interface/types.ts b/src/core/component/decorators/interface.ts similarity index 100% rename from src/core/component/decorators/interface/types.ts rename to src/core/component/decorators/interface.ts diff --git a/src/core/component/decorators/interface/field.ts b/src/core/component/decorators/interface/field.ts deleted file mode 100644 index 93e869f91f..0000000000 --- a/src/core/component/decorators/interface/field.ts +++ /dev/null @@ -1,259 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import type { ComponentInterface } from 'core/component/interface'; -import type { DecoratorFieldWatcher } from 'core/component/decorators/interface/watcher'; -import type { DecoratorFunctionalOptions } from 'core/component/decorators/interface/types'; - -export interface DecoratorSystem< - CTX extends ComponentInterface = ComponentInterface, - A = unknown, - B = A -> extends DecoratorFunctionalOptions { - /** - * Marks the field as unique for each component instance. - * Also, the parameter can take a function that returns a boolean value. - * If this value is true, then the parameter is considered unique. - * - * Please note that the "external" code must provide the uniqueness guarantee - * because V4Fire does not perform special checks for uniqueness. - * - * @default `false` - */ - unique?: boolean | UniqueFieldFn; - - /** - * This option allows you to set the default value of the field. - * But using it, as a rule, is not explicitly required, since the default value can be passed through - * the native syntax of class properties. - * - * Note that if the default value is set using class property syntax, then it is a prototype, not a real value. - * That is, when set to each new instance, it will be cloned using `Object.fastClone`. - * If this behavior does not suit you, then pass the value explicitly via `default` or using the `init` option and - * an initializer function. - * - * @example - * ```typescript - * import iBlock, { component, system } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @system() - * bla: number = 0; - * - * @field({default: 0}) - * bar!: number; - * - * // There will be a trouble here when cloning the value - * @field() - * body: Element = document.body; - * - * // All fine - * @field({default: document.body}) - * validBody!: Element; - * - * // All fine - * @field(() => document.body) - * validBody2!: Element; - * } - * ``` - */ - default?: unknown; - - /** - * A function to initialize the field value. - * The function takes as its first argument a reference to the component context. - * As the second argument, the function takes a reference to a dictionary with other fields of the same type that - * have already been initialized. - * - * @example - * ```typescript - * import iBlock, { component, system } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @system({init: Math.random}) - * hashCode!: number; - * - * @system((ctx, {hashCode}) => String(hashCode)) - * normalizedHashCode!: string; - * } - * ``` - */ - init?: InitFieldFn; - - /** - * A name or a list of names after which this property should be initialized. - * Keep in mind, you can only specify names that are of the same type as the current field (`@system` or `@field`). - * - * @example - * ```typescript - * import iBlock, { component, system } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @system(Math.random) - * hashCode!: number; - * - * @system({ - * after: 'hashCode', - * init: (ctx, {hashCode}) => String(hashCode) - * }) - * - * normalizedHashCode!: string; - * } - * ``` - */ - after?: CanArray; - - /** - * Indicates that property should be initialized before all non-atom properties. - * This option is necessary when you have a field that must be guaranteed to be initialized before other fields, - * and you don't want to use `after` everywhere. But you can still use `after` along with other atomic fields. - * - * @default `false` - * - * @example - * ```typescript - * import Async from 'core/async'; - * import iBlock, { component, system } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @system({atom: true, init: (ctx) => new Async(ctx)}) - * async!: Async; - * - * @system((ctx, data) => data.async.proxy(() => { /* ... *\/ })) - * handler!: Function; - * } - * ``` - */ - atom?: boolean; - - /** - * A watcher or a list of watchers for the current field. - * The watcher can be defined as a component method to invoke, callback function, or watch handle. - * - * The `core/watch` module is used to make objects watchable. - * Therefore, for more information, please refer to its documentation. - * - * @example - * ```typescript - * import iBlock, { component, system } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @system({watch: [ - * 'onIncrement', - * - * (ctx, val, oldVal, info) => - * console.log(val, oldVal, info), - * - * // Also, see core/object/watch - * { - * // If set to false, the handler invoked on the watcher event does not take any arguments from the event - * provideArgs: false, - * - * // How the event handler should be called: - * // - * // 1. `'post'` - the handler will be called on the next tick after the mutation and - * // guaranteed after updating all tied templates; - * // - * // 2. `'pre'` - the handler will be called on the next tick after the mutation and - * // guaranteed before updating all tied templates; - * // - * // 3. `'sync'` - the handler will be invoked immediately after each mutation. - * flush: 'sync', - * - * // Can define as a function too - * handler: 'onIncrement' - * } - * ]}) - * - * i: number = 0; - * - * onIncrement(val, oldVal, info) { - * console.log(val, oldVal, info); - * } - * } - * ``` - */ - watch?: DecoratorFieldWatcher; - - /** - * If set to false, the field can't be watched if created inside a functional component. - * This option is useful when you are writing a superclass or a smart component that can be created - * as regular or functional. - * - * @default `true` - */ - functionalWatching?: boolean; - - /** - * This option is only relevant for functional components. - * The fact is that when a component state changes, all its child functional components are recreated from scratch. - * But we need to restore the state of such components. By default, properties are simply copied from old instances to - * new ones, but sometimes this strategy does not suit us. This option helps here - it allows you to declare that - * a certain property should be mixed based on the old and new values. - * - * Set this property to true to enable the strategy of merging old and new values. - * Or specify a function that will perform the merge. This function takes contexts of the old and new components, - * the name of the field to restore, and optionally, a path to a property to which the given is bound. - * - * @default `false` - */ - merge?: MergeFieldFn | boolean; - - /** - * A dictionary with some extra information of the field. - * You can access this information using `meta.fields` or `meta.systemFields`. - * - * @example - * ```typescript - * import iBlock, { component, system } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @system({init: Math.random, meta: {debug: true}}) - * hashCode!: number; - * - * created() { - * // {debug: true}} - * console.log(this.meta.systemFields.hashCode.meta); - * } - * } - * ``` - */ - meta?: Dictionary; -} - -export interface DecoratorField< - CTX extends ComponentInterface = ComponentInterface, - A = unknown, - B = A -> extends DecoratorSystem { - /** - * If set to true, property changes will cause the template to be guaranteed to be re-rendered. - * Be aware that enabling this property may result in redundant redrawing. - * - * @default `false` - */ - forceUpdate?: boolean; -} - -export interface InitFieldFn { - (ctx: CTX['unsafe'], data: Dictionary): unknown; -} - -export interface MergeFieldFn { - (ctx: CTX['unsafe'], oldCtx: CTX['unsafe'], field: string, link?: string): unknown; -} - -export interface UniqueFieldFn { - (ctx: CTX['unsafe'], oldCtx: CTX['unsafe']): AnyToBoolean; -} diff --git a/src/core/component/decorators/interface/index.ts b/src/core/component/decorators/interface/index.ts deleted file mode 100644 index 68c03af746..0000000000 --- a/src/core/component/decorators/interface/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -export * from 'core/component/decorators/interface/field'; -export * from 'core/component/decorators/interface/accessor'; -export * from 'core/component/decorators/interface/types'; diff --git a/src/core/component/meta/interface/meta.ts b/src/core/component/meta/interface/meta.ts index d3849a17b6..cb4fb109cc 100644 --- a/src/core/component/meta/interface/meta.ts +++ b/src/core/component/meta/interface/meta.ts @@ -6,12 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type { WatchPath } from 'core/object/watch'; - import type { PropOptions } from 'core/component/decorators'; import type { RenderFunction, WritableComputedOptions } from 'core/component/engines'; -import type { ComponentConstructor, WatchObject, ModsDecl } from 'core/component/interface'; +import type { ComponentConstructor, WatchObject, WatchPath, ModsDecl } from 'core/component/interface'; import type { ComponentOptions } from 'core/component/meta/interface/options'; import type { diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index ccea65acd8..500b975f24 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -6,12 +6,11 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type { WatchPath } from 'core/object/watch'; - import type { WritableComputedOptions, DirectiveBinding } from 'core/component/engines'; + import type { PropOptions, InitFieldFn, MergeFieldFn, UniqueFieldFn } from 'core/component/decorators'; -import type { ComponentInterface, FieldWatcher, MethodWatcher, Hook } from 'core/component/interface'; +import type { ComponentInterface, FieldWatcher, MethodWatcher, WatchPath, Hook } from 'core/component/interface'; export interface ComponentProp extends PropOptions { forceUpdate: boolean; From 6cc363fa79c72aba922bedca7c5180001f353109 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 11:26:01 +0300 Subject: [PATCH 209/334] fix: fixes after refactoring --- src/core/component/decorators/prop/decorator.ts | 4 ++-- src/core/component/decorators/system/decorator.ts | 4 ++-- src/core/component/meta/interface/types.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 003f7f3157..59a741df2a 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -60,8 +60,8 @@ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { // Handling the situation when a field changes type during inheritance, // for example, it was a @system in the parent component and became a @prop - for (const key of ['fields', 'systemFields']) { - const cluster = meta[key]; + for (const anotherType of ['fields', 'systemFields']) { + const cluster = meta[anotherType]; if (key in cluster) { const field: ComponentField = {...cluster[key]}; diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index 01f4b02eb2..b0b997eebb 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -72,8 +72,8 @@ export function system( // Handling the situation when a field changes type during inheritance, // for example, it was a @prop in the parent component and became a @system - for (const key of ['props', type === 'fields' ? 'systemFields' : 'fields']) { - const cluster = meta[key]; + for (const anotherType of ['props', type === 'fields' ? 'systemFields' : 'fields']) { + const cluster = meta[anotherType]; if (key in cluster) { const field: ComponentField = {...cluster[key]}; diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index 500b975f24..d8251b6cfc 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -16,7 +16,7 @@ export interface ComponentProp extends PropOptions { forceUpdate: boolean; forceDefault?: boolean; - watchers?: Map; + watchers?: Map; default?: unknown; meta: Dictionary; @@ -38,7 +38,7 @@ export interface ComponentSystemField; merge?: MergeFieldFn | boolean; - watchers?: Map; + watchers?: Map; } export interface ComponentField extends ComponentSystemField { From 378fda68a868a1adaecc39269336c265c6e3b355 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 12:47:11 +0300 Subject: [PATCH 210/334] refactor: optimized meta creation --- src/core/component/meta/create.ts | 2 + src/core/component/meta/fill.ts | 71 +--------- src/core/component/meta/inherit.ts | 64 ++++----- src/core/component/meta/interface/meta.ts | 6 + src/core/component/meta/method.ts | 155 +++++++++++++++------- src/core/component/method/index.ts | 6 +- 6 files changed, 155 insertions(+), 149 deletions(-) diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index 83788b5e2d..a858895427 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -69,6 +69,8 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { renderTriggered: [] }, + metaInitializers: {}, + component: { name: component.name, diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index bf253bad4d..0cf3be6481 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -8,7 +8,6 @@ import { DEFAULT_WRAPPER } from 'core/component/const'; -import { getComponentContext } from 'core/component/context'; import { isAbstractComponent, isBinding } from 'core/component/reflect'; import { addFieldsToMeta } from 'core/component/meta/field'; @@ -72,13 +71,10 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor component, params, - methods, accessors, computedFields, watchers, - hooks, - watchDependencies, watchPropDependencies } = meta; @@ -206,71 +202,8 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor addFieldsToMeta('fields', meta); addFieldsToMeta('systemFields', meta); - // Computed fields - - if (isFirstFill) { - Object.entries(computedFields).forEach(([name, computed]) => { - if (computed == null || computed.cache !== 'auto') { - return; - } - - component.computed[name] = { - get: computed.get, - set: computed.set - }; - }); - } - - // Methods - - Object.entries(methods).forEach(([methodName, method]) => { - if (method == null) { - return; - } - - if (isFirstFill) { - component.methods[methodName] = wrapper; - - if (wrapper.length !== method.fn.length) { - Object.defineProperty(wrapper, 'length', {get: () => method.fn.length}); - } - } - - if (method.watchers != null) { - Object.entries(method.watchers).forEach(([watcherName, watcher]) => { - if (watcher == null || isFunctional && watcher.functional === false) { - return; - } - - const watcherListeners = watchers[watcherName] ?? []; - watchers[watcherName] = watcherListeners; - - watcherListeners.push({ - ...watcher, - method: methodName, - args: Array.toArray(watcher.args), - handler: Object.cast(method.fn) - }); - }); - } - - // Method hooks - - if (method.hooks) { - Object.entries(method.hooks).forEach(([hookName, hook]) => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (hook == null || isFunctional && hook.functional === false) { - return; - } - - hooks[hookName].push({...hook, fn: method.fn}); - }); - } - - function wrapper(this: object) { - // eslint-disable-next-line prefer-rest-params - return method!.fn.apply(getComponentContext(this), arguments); - } + Object.values(meta.metaInitializers).forEach((init) => { + init?.(meta); }); // Modifiers diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 823db3ac46..59da421aed 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -22,6 +22,7 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com const decoratedKeys = componentDecoratedKeys[meta.componentName]; Object.assign(meta.tiedFields, parentMeta.tiedFields); + Object.assign(meta.metaInitializers, parentMeta.metaInitializers); if (parentMeta.watchDependencies.size > 0) { meta.watchDependencies = new Map(parentMeta.watchDependencies); @@ -37,6 +38,7 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com inheritAccessors(meta.computedFields, parentMeta.computedFields); inheritMethods(meta.methods, parentMeta.methods); + Object.assign(meta.component.methods, parentMeta.component.methods); if (meta.params.partial == null) { inheritMods(meta, parentMeta); @@ -45,85 +47,85 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com return meta; function inheritProp(current: ComponentMeta['props'], parent: ComponentMeta['props']) { - Object.entries(parent).forEach(([propName, parent]) => { - if (parent == null) { - return; + for (const [propName, parentProp] of Object.entries(parent)) { + if (parentProp == null) { + continue; } if (decoratedKeys == null || !decoratedKeys.has(propName)) { - current[propName] = parent; - return; + current[propName] = parentProp; + continue; } let watchers: CanUndef>; - parent.watchers?.forEach((watcher: FieldWatcher) => { + parentProp.watchers?.forEach((watcher: FieldWatcher) => { watchers ??= new Map(); watchers.set(watcher.handler, {...watcher}); }); - current[propName] = {...parent, watchers}; - }); + current[propName] = {...parentProp, watchers}; + } } function inheritField(current: ComponentMeta['fields'], parent: ComponentMeta['fields']) { - Object.entries(parent).forEach(([fieldName, parent]) => { - if (parent == null) { - return; + for (const [fieldName, parentField] of Object.entries(parent)) { + if (parentField == null) { + continue; } if (decoratedKeys == null || !decoratedKeys.has(fieldName)) { - current[fieldName] = parent; - return; + current[fieldName] = parentField; + continue; } let after: CanUndef>, watchers: CanUndef>; - parent.watchers?.forEach((watcher: FieldWatcher) => { + parentField.watchers?.forEach((watcher: FieldWatcher) => { watchers ??= new Map(); watchers.set(watcher.handler, {...watcher}); }); - parent.after?.forEach((name: string) => { + parentField.after?.forEach((name: string) => { after ??= new Set(); after.add(name); }); - current[fieldName] = {...parent, after, watchers}; - }); + current[fieldName] = {...parentField, after, watchers}; + } } function inheritAccessors(current: ComponentMeta['accessors'], parent: ComponentMeta['accessors']) { - Object.entries(parent).forEach(([accessorName, parent]) => { - current[accessorName] = {...parent!}; - }); + for (const [accessorName, parentAccessor] of Object.entries(parent)) { + current[accessorName] = {...parentAccessor!}; + } } function inheritMethods(current: ComponentMeta['methods'], parent: ComponentMeta['methods']) { - Object.entries(parent).forEach(([methodName, parent]) => { - if (parent == null) { - return; + for (const [methodName, parentMethod] of Object.entries(parent)) { + if (parentMethod == null) { + continue; } if (decoratedKeys == null || !decoratedKeys.has(methodName)) { - current[methodName] = {...parent}; - return; + current[methodName] = {...parentMethod}; + continue; } const watchers = {}, hooks = {}; - if (parent.watchers != null) { - Object.entries(parent.watchers).forEach(([key, val]) => { + if (parentMethod.watchers != null) { + Object.entries(parentMethod.watchers).forEach(([key, val]) => { watchers[key] = {...val}; }); } - if (parent.hooks != null) { - Object.entries(parent.hooks).forEach(([key, hook]) => { + if (parentMethod.hooks != null) { + Object.entries(parentMethod.hooks).forEach(([key, hook]) => { hooks[key] = { ...hook, after: Object.size(hook.after) > 0 ? new Set(hook.after) : undefined @@ -131,8 +133,8 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com }); } - current[methodName] = {...parent, watchers, hooks}; - }); + current[methodName] = {...parentMethod, watchers, hooks}; + } } } diff --git a/src/core/component/meta/interface/meta.ts b/src/core/component/meta/interface/meta.ts index cb4fb109cc..0f1fb0e631 100644 --- a/src/core/component/meta/interface/meta.ts +++ b/src/core/component/meta/interface/meta.ts @@ -153,6 +153,12 @@ export interface ComponentMeta { */ hooks: ComponentHooks; + /** + * A dictionary containing functions to initialize the component metaobject. + * The keys in the dictionary are the component entities: props, fields, methods, etc. + */ + metaInitializers: Dictionary<(meta: ComponentMeta) => void>; + /** * A less abstract representation of the component would typically include the following elements, * which are useful for building component libraries: diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index 849d5e0442..8d25cc1a81 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -7,7 +7,9 @@ */ import { defProp } from 'core/const/props'; -import type { ComponentMeta } from 'core/component/interface'; + +import { getComponentContext } from 'core/component/context'; +import type { ComponentMeta, ComponentAccessor, ComponentMethod } from 'core/component/interface'; const ALREADY_PASSED = Symbol('This target is passed'); @@ -27,33 +29,88 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me Object.defineProperty(constructor, ALREADY_PASSED, {value: true}); const { + component, componentName: src, + props, fields, - computedFields, systemFields, + + computedFields, accessors, - methods + methods, + + metaInitializers } = meta; const proto = constructor.prototype, + parentProto = Object.getPrototypeOf(proto), descriptors = Object.getOwnPropertyDescriptors(proto); - Object.entries(descriptors).forEach(([name, desc]) => { + for (const [name, desc] of Object.entries(descriptors)) { if (name === 'constructor') { - return; + continue; } // Methods if ('value' in desc) { - const fn = desc.value; + const method = desc.value; + + if (!Object.isFunction(method)) { + continue; + } + + const methodDesc: ComponentMethod = Object.assign(methods[name] ?? {watchers: {}, hooks: {}}, {src, fn: method}); + methods[name] = methodDesc; + + component.methods[name] = method; + + // eslint-disable-next-line func-style + const wrapper = function wrapper(this: object) { + // eslint-disable-next-line prefer-rest-params + return method.apply(getComponentContext(this), arguments); + }; - if (!Object.isFunction(fn)) { - return; + if (wrapper.length !== method.length) { + Object.defineProperty(wrapper, 'length', {get: () => method.length}); } - methods[name] = Object.assign(methods[name] ?? {watchers: {}, hooks: {}}, {src, fn}); + component.methods[name] = wrapper; + + const + watchers = methodDesc.watchers != null ? Object.entries(methodDesc.watchers) : [], + hooks = methodDesc.hooks != null ? Object.entries(methodDesc.hooks) : []; + + if (watchers.length > 0 || hooks.length > 0) { + metaInitializers[name] = (meta) => { + const isFunctional = meta.params.functional === true; + + for (const [watcherName, watcher] of watchers) { + if (watcher == null || isFunctional && watcher.functional === false) { + continue; + } + + const watcherListeners = meta.watchers[watcherName] ?? []; + meta.watchers[watcherName] = watcherListeners; + + watcherListeners.push({ + ...watcher, + method: name, + args: Array.toArray(watcher.args), + handler: method + }); + } + + for (const [hookName, hook] of hooks) { + if (isFunctional && hook.functional === false) { + continue; + } + + meta.hooks[hookName].push({...hook, fn: method}); + } + }; + } // Accessors } else { @@ -62,18 +119,15 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me storeKey = `${name}Store`; let - metaKey: string, - tiedWith: CanUndef; + type: 'accessors' | 'computedFields' = 'accessors', + tiedWith: CanNull = null; // Computed fields are cached by default if ( name in computedFields || !(name in accessors) && (tiedWith = props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) ) { - metaKey = 'computedFields'; - - } else { - metaKey = 'accessors'; + type = 'computedFields'; } let field: Dictionary; @@ -88,7 +142,7 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me field = systemFields; } - const store = meta[metaKey]; + const store = meta[type]; // If we already have a property by this key, like a prop or field, // we need to delete it to correct override @@ -102,48 +156,57 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me set = desc.set ?? old?.set, get = desc.get ?? old?.get; - // To use `super` within the setter, we also create a new method with a name `${key}Setter` - if (set != null) { - const nm = `${name}Setter`; - proto[nm] = set; - - meta.methods[nm] = { - src, - fn: set, - watchers: {}, - hooks: {} - }; + if (name in parentProto) { + // To use `super` within the setter, we also create a new method with a name `${key}Setter` + if (set != null) { + const methodName = `${name}Setter`; + proto[methodName] = set; + + meta.methods[methodName] = { + src, + fn: set, + watchers: {}, + hooks: {} + }; + } + + // To using `super` within the getter, we also create a new method with a name `${key}Getter` + if (get != null) { + const methodName = `${name}Getter`; + proto[methodName] = get; + + meta.methods[methodName] = { + src, + fn: get, + watchers: {}, + hooks: {} + }; + } } - // To using `super` within the getter, we also create a new method with a name `${key}Getter` - if (get != null) { - const nm = `${name}Getter`; - proto[nm] = get; - - meta.methods[nm] = { - src, - fn: get, - watchers: {}, - hooks: {} - }; - } - - const acc = Object.assign(store[name] ?? {}, { + const accessor: ComponentAccessor = Object.assign(store[name] ?? {cache: false}, { src, get: desc.get ?? old?.get, set }); - store[name] = acc; + store[name] = accessor; + + if (accessor.cache === 'auto') { + component.computed[name] = { + get: accessor.get, + set: accessor.set + }; + } // eslint-disable-next-line eqeqeq - if (acc.functional === undefined && meta.params.functional === null) { - acc.functional = false; + if (accessor.functional === undefined && meta.params.functional === null) { + accessor.functional = false; } if (tiedWith != null) { - acc.tiedWith = tiedWith; + accessor.tiedWith = tiedWith; } } - }); + } } diff --git a/src/core/component/method/index.ts b/src/core/component/method/index.ts index 450e209782..b78dbef3b1 100644 --- a/src/core/component/method/index.ts +++ b/src/core/component/method/index.ts @@ -22,13 +22,13 @@ export function attachMethodsFromMeta(component: ComponentInterface): void { const isFunctional = meta.params.functional === true; - Object.entries(methods).forEach(([name, method]) => { + for (const [name, method] of Object.entries(methods)) { if (method == null || !SSR && isFunctional && method.functional === false) { - return; + continue; } component[name] = method.fn.bind(component); - }); + } if (isFunctional) { component.render = Object.cast(meta.component.render); From ee10fba029e4ce4f8e846d86f06d46997c90e105 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 12:54:58 +0300 Subject: [PATCH 211/334] chore: better naming --- .../decorators/computed/decorator.ts | 24 ++++++------ .../component/decorators/hook/decorator.ts | 10 ++--- .../component/decorators/system/decorator.ts | 38 +++++++++---------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts index 487c18ca40..6f113b5e9c 100644 --- a/src/core/component/decorators/computed/decorator.ts +++ b/src/core/component/decorators/computed/decorator.ts @@ -34,15 +34,15 @@ import type { DecoratorComputed } from 'core/component/decorators/computed/inter * ``` */ export function computed(params?: DecoratorComputed): PartDecorator { - return createComponentDecorator(({meta}, key, desc) => { + return createComponentDecorator(({meta}, accessorName, desc) => { if (desc == null) { return; } params = {...params}; - delete meta.accessors[key]; - delete meta.computedFields[key]; + delete meta.accessors[accessorName]; + delete meta.computedFields[accessorName]; let type: 'accessors' | 'computedFields' = 'accessors'; @@ -50,24 +50,24 @@ export function computed(params?: DecoratorComputed): PartDecorator { params.cache === true || params.cache === 'auto' || params.cache === 'forever' || - params.cache !== false && (Object.isArray(params.dependencies) || key in meta.computedFields) + params.cache !== false && (Object.isArray(params.dependencies) || accessorName in meta.computedFields) ) { type = 'computedFields'; } - const accessor: ComponentAccessor = meta[type][key] ?? { + let accessor: ComponentAccessor = meta[type][accessorName] ?? { src: meta.componentName, cache: false }; const needOverrideComputed = type === 'accessors' ? - key in meta.computedFields : - !('cache' in params) && key in meta.accessors; + accessorName in meta.computedFields : + !('cache' in params) && accessorName in meta.accessors; if (needOverrideComputed) { - const computed = meta.computedFields[key]; + const computed = meta.computedFields[accessorName]; - meta[type][key] = normalizeFunctionalParams({ + accessor = normalizeFunctionalParams({ ...computed, ...params, src: computed?.src ?? accessor.src, @@ -75,15 +75,17 @@ export function computed(params?: DecoratorComputed): PartDecorator { }, meta); } else { - meta[type][key] = normalizeFunctionalParams({ + accessor = normalizeFunctionalParams({ ...accessor, ...params, cache: type === 'computedFields' ? params.cache ?? true : false }, meta); } + meta[type][accessorName] = accessor; + if (params.dependencies != null && params.dependencies.length > 0) { - meta.watchDependencies.set(key, params.dependencies); + meta.watchDependencies.set(accessorName, params.dependencies); } }); } diff --git a/src/core/component/decorators/hook/decorator.ts b/src/core/component/decorators/hook/decorator.ts index fc3ecaaa7c..48053ebd0e 100644 --- a/src/core/component/decorators/hook/decorator.ts +++ b/src/core/component/decorators/hook/decorator.ts @@ -35,14 +35,14 @@ import type { DecoratorHook } from 'core/component/decorators/hook/interface'; * ``` */ export function hook(hook: DecoratorHook): PartDecorator { - return createComponentDecorator(({meta}, key, desc) => { + return createComponentDecorator(({meta}, methodName, desc) => { if (desc == null) { return; } const methodHooks = Array.toArray(hook); - const method: ComponentMethod = meta.methods[key] ?? { + const method: ComponentMethod = meta.methods[methodName] ?? { src: meta.componentName, fn: Object.throw, hooks: {} @@ -58,16 +58,16 @@ export function hook(hook: DecoratorHook): PartDecorator { hooks[hookName] = normalizeFunctionalParams({ ...hookParams, - name: key, + name: methodName, hook: hookName, after: hookParams.after != null ? new Set(Array.toArray(hookParams.after)) : undefined }, meta); } else { - hooks[hook] = normalizeFunctionalParams({name: key, hook}, meta); + hooks[hook] = normalizeFunctionalParams({name: methodName, hook}, meta); } } - meta.methods[key] = normalizeFunctionalParams({...method}, meta); + meta.methods[methodName] = normalizeFunctionalParams({...method}, meta); }); } diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index b0b997eebb..404c72ac72 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -54,20 +54,20 @@ export function system( initOrParams?: InitFieldFn | DecoratorSystem | DecoratorField, type: 'fields' | 'systemFields' = 'systemFields' ): PartDecorator { - return createComponentDecorator(({meta}, key) => { + return createComponentDecorator(({meta}, fieldName) => { const params = Object.isFunction(initOrParams) ? {init: initOrParams} : {...initOrParams}; - delete meta.methods[key]; - delete meta.accessors[key]; - delete meta.computedFields[key]; + delete meta.methods[fieldName]; + delete meta.accessors[fieldName]; + delete meta.computedFields[fieldName]; - const accessors = meta.accessors[key] != null ? + const accessors = meta.accessors[fieldName] != null ? meta.accessors : meta.computedFields; - if (accessors[key] != null) { - Object.defineProperty(meta.constructor.prototype, key, defProp); - delete accessors[key]; + if (accessors[fieldName] != null) { + Object.defineProperty(meta.constructor.prototype, fieldName, defProp); + delete accessors[fieldName]; } // Handling the situation when a field changes type during inheritance, @@ -75,27 +75,27 @@ export function system( for (const anotherType of ['props', type === 'fields' ? 'systemFields' : 'fields']) { const cluster = meta[anotherType]; - if (key in cluster) { - const field: ComponentField = {...cluster[key]}; + if (fieldName in cluster) { + const field: ComponentField = {...cluster[fieldName]}; // Do not inherit the `functional` option in this case delete field.functional; - if (key === 'props') { + if (fieldName === 'props') { if (Object.isFunction(field.default)) { field.init = field.default; delete field.default; } } - meta[type][key] = field; - delete cluster[key]; + meta[type][fieldName] = field; + delete cluster[fieldName]; break; } } - const field: ComponentField = meta[type][key] ?? { + const field: ComponentField = meta[type][fieldName] ?? { src: meta.componentName, meta: {} }; @@ -121,7 +121,7 @@ export function system( } } - meta[type][key] = normalizeFunctionalParams({ + meta[type][fieldName] = normalizeFunctionalParams({ ...field, ...params, @@ -134,10 +134,10 @@ export function system( } }, meta); - if (isStore.test(key)) { - const tiedWith = isStore.replace(key); - meta.tiedFields[key] = tiedWith; - meta.tiedFields[tiedWith] = key; + if (isStore.test(fieldName)) { + const tiedWith = isStore.replace(fieldName); + meta.tiedFields[fieldName] = tiedWith; + meta.tiedFields[tiedWith] = fieldName; } }); } From 7f5bf30a5ba27988caa253d13eee48170e9f005b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 15:53:56 +0300 Subject: [PATCH 212/334] fix: fixes after refactoring & optimization --- .../decorators/computed/decorator.ts | 7 +- .../decorators/computed/interface.ts | 4 +- .../component/decorators/prop/decorator.ts | 128 ++++++++++++-- .../component/decorators/system/decorator.ts | 6 +- src/core/component/meta/create.ts | 19 +- src/core/component/meta/field/index.ts | 26 +-- src/core/component/meta/fill.ts | 165 ++---------------- src/core/component/meta/fork.ts | 12 +- src/core/component/meta/inherit.ts | 83 +++++---- src/core/component/meta/interface/meta.ts | 6 +- src/core/component/meta/interface/types.ts | 4 +- src/core/component/meta/method.ts | 27 ++- 12 files changed, 256 insertions(+), 231 deletions(-) diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts index 6f113b5e9c..7694d878f1 100644 --- a/src/core/component/decorators/computed/decorator.ts +++ b/src/core/component/decorators/computed/decorator.ts @@ -41,8 +41,9 @@ export function computed(params?: DecoratorComputed): PartDecorator { params = {...params}; - delete meta.accessors[accessorName]; - delete meta.computedFields[accessorName]; + delete meta.props[accessorName]; + delete meta.fields[accessorName]; + delete meta.systemFields[accessorName]; let type: 'accessors' | 'computedFields' = 'accessors'; @@ -82,6 +83,8 @@ export function computed(params?: DecoratorComputed): PartDecorator { }, meta); } + delete meta[type === 'computedFields' ? 'accessors' : 'computedFields'][accessorName]; + meta[type][accessorName] = accessor; if (params.dependencies != null && params.dependencies.length > 0) { diff --git a/src/core/component/decorators/computed/interface.ts b/src/core/component/decorators/computed/interface.ts index 0fca9ad149..5d9403b764 100644 --- a/src/core/component/decorators/computed/interface.ts +++ b/src/core/component/decorators/computed/interface.ts @@ -6,7 +6,9 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import type { WatchPath, ComponentAccessorCacheType } from 'core/component/interface'; +import type { WatchPath } from 'core/object/watch'; + +import type { ComponentAccessorCacheType } from 'core/component/interface'; import type { DecoratorFunctionalOptions } from 'core/component/decorators/interface'; export interface DecoratorComputed extends DecoratorFunctionalOptions { diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 59a741df2a..e94a1d7367 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -7,7 +7,9 @@ */ import { defProp } from 'core/const/props'; +import { DEFAULT_WRAPPER } from 'core/component/const'; +import { isBinding } from 'core/component/reflect'; import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; import type { ComponentProp, ComponentField } from 'core/component/interface'; @@ -40,22 +42,20 @@ import type { DecoratorProp, PropType } from 'core/component/decorators/prop/int * ``` */ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { - return createComponentDecorator(({meta}, key) => { + return createComponentDecorator(({meta}, propName) => { const params: DecoratorProp = Object.isFunction(typeOrParams) || Object.isArray(typeOrParams) ? {type: typeOrParams, forceUpdate: true} : {forceUpdate: true, ...typeOrParams}; - delete meta.methods[key]; - delete meta.accessors[key]; - delete meta.computedFields[key]; + delete meta.methods[propName]; - const accessors = meta.accessors[key] != null ? + const accessors = meta.accessors[propName] != null ? meta.accessors : meta.computedFields; - if (accessors[key] != null) { - Object.defineProperty(meta.constructor.prototype, key, defProp); - delete accessors[key]; + if (accessors[propName] != null) { + Object.defineProperty(meta.constructor.prototype, propName, defProp); + delete accessors[propName]; } // Handling the situation when a field changes type during inheritance, @@ -63,8 +63,8 @@ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { for (const anotherType of ['fields', 'systemFields']) { const cluster = meta[anotherType]; - if (key in cluster) { - const field: ComponentField = {...cluster[key]}; + if (propName in cluster) { + const field: ComponentField = {...cluster[propName]}; // Do not inherit the `functional` option in this case delete field.functional; @@ -72,15 +72,15 @@ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { // The option `init` cannot be converted to `default` delete field.init; - meta.props[key] = {...field, forceUpdate: true}; + meta.props[propName] = {...field, forceUpdate: true}; - delete cluster[key]; + delete cluster[propName]; break; } } - const prop: ComponentProp = meta.props[key] ?? { + let prop: ComponentProp = meta.props[propName] ?? { forceUpdate: true, meta: {} }; @@ -102,7 +102,7 @@ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { } } - meta.props[key] = normalizeFunctionalParams({ + prop = normalizeFunctionalParams({ ...prop, ...params, @@ -113,5 +113,105 @@ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { ...params.meta } }, meta); + + meta.props[propName] = prop; + + let defaultValue: unknown; + + const + isRoot = meta.params.root === true, + isFunctional = meta.params.functional === true, + defaultProps = meta.params.defaultProps !== false; + + if (defaultProps || prop.forceDefault) { + if (prop.default !== undefined) { + defaultValue = prop.default; + + } else { + const defaultInstanceValue = meta.instance[propName]; + + let getDefault = defaultInstanceValue; + + // If the default value of a prop is set via a default value for a class property, + // it is necessary to clone this value for each new component instance + // to ensure that they do not share the same value + if (prop.type !== Function && defaultInstanceValue != null && typeof defaultInstanceValue === 'object') { + getDefault = () => Object.isPrimitive(defaultInstanceValue) ? + defaultInstanceValue : + Object.fastClone(defaultInstanceValue); + + (getDefault)[DEFAULT_WRAPPER] = true; + } + + defaultValue = getDefault; + } + } + + if (!isRoot || defaultValue !== undefined) { + const {component} = meta; + + (prop.forceUpdate ? component.props : component.attrs)[propName] = { + type: prop.type, + required: prop.required !== false && defaultProps && defaultValue === undefined, + + default: defaultValue, + functional: prop.functional, + + // eslint-disable-next-line @v4fire/unbound-method + validator: prop.validator + }; + } + + const canWatchProps = !SSR && !isRoot && !isFunctional; + + if (canWatchProps || watchers != null && watchers.size > 0) { + meta.metaInitializers.set(propName, (meta) => { + const {watchPropDependencies} = meta; + + const + isFunctional = meta.params.functional === true, + canWatchProps = !SSR && !isRoot && !isFunctional; + + const watcherListeners = meta.watchers[propName] ?? []; + meta.watchers[propName] = watcherListeners; + + if (watchers != null) { + for (const watcher of watchers.values()) { + if (isFunctional && watcher.functional === false || !canWatchProps && !watcher.immediate) { + continue; + } + + watcherListeners.push(watcher); + } + } + + if (canWatchProps) { + const normalizedName = isBinding.test(propName) ? isBinding.replace(propName) : propName; + + if ((meta.computedFields[normalizedName] ?? meta.accessors[normalizedName]) != null) { + const props = watchPropDependencies.get(normalizedName) ?? new Set(); + + props.add(propName); + watchPropDependencies.set(normalizedName, props); + + } else { + for (const [path, deps] of meta.watchDependencies) { + for (const dep of deps) { + const pathChunks = Object.isArray(dep) ? dep : dep.split('.', 1); + + if (pathChunks[0] === propName) { + const props = watchPropDependencies.get(path) ?? new Set(); + + props.add(propName); + watchPropDependencies.set(path, props); + + break; + } + } + } + } + } + }); + } }); } diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index 404c72ac72..3bb23a5973 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -58,8 +58,6 @@ export function system( const params = Object.isFunction(initOrParams) ? {init: initOrParams} : {...initOrParams}; delete meta.methods[fieldName]; - delete meta.accessors[fieldName]; - delete meta.computedFields[fieldName]; const accessors = meta.accessors[fieldName] != null ? meta.accessors : @@ -81,7 +79,9 @@ export function system( // Do not inherit the `functional` option in this case delete field.functional; - if (fieldName === 'props') { + if (anotherType === 'props') { + delete meta.component.props[fieldName]; + if (Object.isFunction(field.default)) { field.init = field.default; delete field.default; diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index a858895427..86fd2eb840 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -12,6 +12,8 @@ import { inheritMeta } from 'core/component/meta/inherit'; import type { ComponentMeta, ComponentConstructorInfo } from 'core/component/interface'; +const INSTANCE = Symbol('The component instance'); + /** * Creates a component metaobject based on the information from its constructor, and then returns this object * @param component - information obtained from the component constructor using the `getInfoFromConstructor` function @@ -19,14 +21,23 @@ import type { ComponentMeta, ComponentConstructorInfo } from 'core/component/int export function createMeta(component: ComponentConstructorInfo): ComponentMeta { const meta: ComponentMeta = { name: component.name, - layer: component.layer, componentName: component.componentName, + layer: component.layer, + params: component.params, parentMeta: component.parentMeta, + constructor: component.constructor, - instance: {}, - params: component.params, + get instance() { + const {constructor} = this; + + if (!constructor.hasOwnProperty(INSTANCE)) { + Object.defineProperty(constructor, INSTANCE, {value: new constructor()}); + } + + return constructor[INSTANCE]; + }, props: {}, mods: component.params.partial == null ? getComponentMods(component) : {}, @@ -69,7 +80,7 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { renderTriggered: [] }, - metaInitializers: {}, + metaInitializers: new Map(), component: { name: component.name, diff --git a/src/core/component/meta/field/index.ts b/src/core/component/meta/field/index.ts index 44706340f5..82a01e67b8 100644 --- a/src/core/component/meta/field/index.ts +++ b/src/core/component/meta/field/index.ts @@ -26,9 +26,11 @@ export function addFieldsToMeta(type: 'fields' | 'systemFields', meta: Component fields = meta[type], fieldInitializers = type === 'fields' ? meta.fieldInitializers : meta.systemFieldInitializers; - Object.entries(fields).forEach(([fieldName, field]) => { + for (const fieldName of Object.keys(fields)) { + const field = fields[fieldName]; + if (field == null || !SSR && isFunctional && field.functional === false) { - return; + continue; } let @@ -75,17 +77,19 @@ export function addFieldsToMeta(type: 'fields' | 'systemFields', meta: Component fieldInitializers.push([fieldName, getValue]); - field.watchers?.forEach((watcher) => { - if (isFunctional && watcher.functional === false) { - return; - } + if (field.watchers != null) { + for (const watcher of field.watchers.values()) { + if (isFunctional && watcher.functional === false) { + continue; + } - const watcherListeners = watchers[fieldName] ?? []; - watchers[fieldName] = watcherListeners; + const watcherListeners = watchers[fieldName] ?? []; + watchers[fieldName] = watcherListeners; - watcherListeners.push(watcher); - }); - }); + watcherListeners.push(watcher); + } + } + } sortFields(fieldInitializers, fields); } diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 0cf3be6481..9e137c74f6 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -6,9 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { DEFAULT_WRAPPER } from 'core/component/const'; - -import { isAbstractComponent, isBinding } from 'core/component/reflect'; +import { isAbstractComponent } from 'core/component/reflect'; import { addFieldsToMeta } from 'core/component/meta/field'; import { addMethodsToMeta } from 'core/component/meta/method'; @@ -17,7 +15,6 @@ import type { ComponentConstructor, ModVal } from 'core/component/interface'; import type { ComponentMeta } from 'core/component/meta/interface'; const - INSTANCE = Symbol('The component instance'), BLUEPRINT = Symbol('The metaobject blueprint'), ALREADY_FILLED = Symbol('This constructor has already been used to populate the metaobject'); @@ -55,9 +52,9 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor if (blueprint != null) { const hooks = {}; - Object.entries(blueprint.hooks).forEach(([name, handlers]) => { - hooks[name] = handlers.slice(); - }); + for (const name of Object.keys(blueprint.hooks)) { + hooks[name] = blueprint.hooks[name].slice(); + } Object.assign(meta, { hooks, @@ -67,166 +64,36 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor }); } - const { - component, - params, - - accessors, - computedFields, - - watchers, - watchDependencies, - watchPropDependencies - } = meta; - - Object.defineProperty(meta, 'instance', { - enumerable: true, - configurable: true, - - get() { - if (!constructor.hasOwnProperty(INSTANCE)) { - Object.defineProperty(constructor, INSTANCE, {value: new constructor()}); - } - - return constructor[INSTANCE]; - } - }); - - // Creating an instance of a component is not a free operation. - // If it is not immediately necessary, we execute it in the background during idle time. - requestIdleCallback(() => { - void meta.instance; - }); - - const - isRoot = params.root === true, - isFunctional = params.functional === true; - - // Props - - const - defaultProps = params.defaultProps !== false, - canWatchProps = !SSR && !isRoot && !isFunctional; - - Object.entries(meta.props).forEach(([propName, prop]) => { - if (prop == null) { - return; - } - - if (isFirstFill) { - const skipDefault = !defaultProps && !prop.forceDefault; - - let defaultValue: unknown; - - if (!skipDefault) { - if (prop.default !== undefined) { - defaultValue = prop.default; - - } else { - const defaultInstanceValue = meta.instance[propName]; - - let getDefault = defaultInstanceValue; - - // If the default value of a prop is set via a default value for a class property, - // it is necessary to clone this value for each new component instance - // to ensure that they do not share the same value - if (prop.type !== Function && defaultInstanceValue != null && typeof defaultInstanceValue === 'object') { - getDefault = () => Object.isPrimitive(defaultInstanceValue) ? - defaultInstanceValue : - Object.fastClone(defaultInstanceValue); - - (getDefault)[DEFAULT_WRAPPER] = true; - } - - defaultValue = getDefault; - } - } - - if (!isRoot || defaultValue !== undefined) { - (prop.forceUpdate ? component.props : component.attrs)[propName] = { - type: prop.type, - required: prop.required !== false && defaultProps && defaultValue === undefined, - - default: defaultValue, - functional: prop.functional, - - // eslint-disable-next-line @v4fire/unbound-method - validator: prop.validator - }; - } - } - - if (prop.watchers != null && Object.size(prop.watchers) > 0) { - const watcherListeners = watchers[propName] ?? []; - watchers[propName] = watcherListeners; - - prop.watchers.forEach((watcher) => { - if (isFunctional && watcher.functional === false || !canWatchProps && !watcher.immediate) { - return; - } - - watcherListeners.push(watcher); - }); - } - - if (canWatchProps) { - const normalizedName = isBinding.test(propName) ? isBinding.replace(propName) : propName; - - if ((computedFields[normalizedName] ?? accessors[normalizedName]) != null) { - const props = watchPropDependencies.get(normalizedName) ?? new Set(); - - props.add(propName); - watchPropDependencies.set(normalizedName, props); - - } else { - watchDependencies.forEach((deps, path) => { - deps.some((dep) => { - const pathChunks = Object.isArray(dep) ? dep : dep.split('.', 1); - - if (pathChunks[0] === propName) { - const props = watchPropDependencies.get(path) ?? new Set(); - - props.add(propName); - watchPropDependencies.set(path, props); - - return true; - } - - return false; - }); - }); - } - } - }); + const {component} = meta; addFieldsToMeta('fields', meta); addFieldsToMeta('systemFields', meta); - Object.values(meta.metaInitializers).forEach((init) => { - init?.(meta); - }); + for (const init of meta.metaInitializers.values()) { + init(meta); + } // Modifiers if (isFirstFill) { const {mods} = component; - Object.entries(meta.mods).forEach(([modsName, mod]) => { + for (const modName of Object.keys(meta.mods)) { + const mod = meta.mods[modName]; + let defaultValue: CanUndef; if (mod != null) { - mod.some((val) => { + for (const val of mod) { if (Object.isArray(val)) { defaultValue = val; - return true; + break; } + } - return false; - }); - - mods[modsName] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; + mods[modName] = defaultValue !== undefined ? String(defaultValue[0]) : undefined; } - }); + } } Object.defineProperty(constructor, ALREADY_FILLED, {value: true}); diff --git a/src/core/component/meta/fork.ts b/src/core/component/meta/fork.ts index 18ffec176e..6fd1a9b57c 100644 --- a/src/core/component/meta/fork.ts +++ b/src/core/component/meta/fork.ts @@ -21,17 +21,19 @@ export function forkMeta(base: ComponentMeta): ComponentMeta { meta.tiedFields = {...meta.tiedFields}; meta.hooks = {}; - Object.entries(base.hooks).forEach(([name, handlers]) => { - meta.hooks[name] = handlers.slice(); - }); + for (const name of Object.keys(base.hooks)) { + meta.hooks[name] = base.hooks[name].slice(); + } meta.watchers = {}; - Object.entries(base.watchers).forEach(([name, watchers]) => { + for (const name of Object.keys(base.watchers)) { + const watchers = base.watchers[name]; + if (watchers != null) { meta.watchers[name] = watchers.slice(); } - }); + } return meta; } diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 59da421aed..9fa8eb48b3 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -22,20 +22,27 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com const decoratedKeys = componentDecoratedKeys[meta.componentName]; Object.assign(meta.tiedFields, parentMeta.tiedFields); - Object.assign(meta.metaInitializers, parentMeta.metaInitializers); + + if (parentMeta.metaInitializers.size > 0) { + meta.metaInitializers = new Map(parentMeta.metaInitializers); + } if (parentMeta.watchDependencies.size > 0) { meta.watchDependencies = new Map(parentMeta.watchDependencies); } inheritParams(meta, parentMeta); + inheritProp(meta.props, parentMeta.props); + Object.assign(meta.component.props, parentMeta.component.props); + Object.assign(meta.component.attrs, parentMeta.component.attrs); inheritField(meta.fields, parentMeta.fields); inheritField(meta.systemFields, parentMeta.systemFields); inheritAccessors(meta.accessors, parentMeta.accessors); inheritAccessors(meta.computedFields, parentMeta.computedFields); + Object.assign(meta.component.computed, parentMeta.component.computed); inheritMethods(meta.methods, parentMeta.methods); Object.assign(meta.component.methods, parentMeta.component.methods); @@ -47,7 +54,9 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com return meta; function inheritProp(current: ComponentMeta['props'], parent: ComponentMeta['props']) { - for (const [propName, parentProp] of Object.entries(parent)) { + for (const propName of Object.keys(parent)) { + const parentProp = parent[propName]; + if (parentProp == null) { continue; } @@ -59,17 +68,21 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com let watchers: CanUndef>; - parentProp.watchers?.forEach((watcher: FieldWatcher) => { - watchers ??= new Map(); - watchers.set(watcher.handler, {...watcher}); - }); + if (parentProp.watchers != null) { + for (const watcher of parentProp.watchers.values()) { + watchers ??= new Map(); + watchers.set(watcher.handler, {...watcher}); + } + } current[propName] = {...parentProp, watchers}; } } function inheritField(current: ComponentMeta['fields'], parent: ComponentMeta['fields']) { - for (const [fieldName, parentField] of Object.entries(parent)) { + for (const fieldName of Object.keys(parent)) { + const parentField = parent[fieldName]; + if (parentField == null) { continue; } @@ -83,28 +96,34 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com after: CanUndef>, watchers: CanUndef>; - parentField.watchers?.forEach((watcher: FieldWatcher) => { - watchers ??= new Map(); - watchers.set(watcher.handler, {...watcher}); - }); + if (parentField.watchers != null) { + for (const watcher of parentField.watchers.values()) { + watchers ??= new Map(); + watchers.set(watcher.handler, {...watcher}); + } + } - parentField.after?.forEach((name: string) => { - after ??= new Set(); - after.add(name); - }); + if (parentField.after != null) { + for (const name of parentField.after) { + after ??= new Set(); + after.add(name); + } + } current[fieldName] = {...parentField, after, watchers}; } } function inheritAccessors(current: ComponentMeta['accessors'], parent: ComponentMeta['accessors']) { - for (const [accessorName, parentAccessor] of Object.entries(parent)) { - current[accessorName] = {...parentAccessor!}; + for (const accessorName of Object.keys(parent)) { + current[accessorName] = {...parent[accessorName]!}; } } function inheritMethods(current: ComponentMeta['methods'], parent: ComponentMeta['methods']) { - for (const [methodName, parentMethod] of Object.entries(parent)) { + for (const methodName of Object.keys(parent)) { + const parentMethod = parent[methodName]; + if (parentMethod == null) { continue; } @@ -119,18 +138,20 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com hooks = {}; if (parentMethod.watchers != null) { - Object.entries(parentMethod.watchers).forEach(([key, val]) => { - watchers[key] = {...val}; - }); + for (const key of Object.keys(parentMethod.watchers)) { + watchers[key] = {...parentMethod.watchers[key]}; + } } if (parentMethod.hooks != null) { - Object.entries(parentMethod.hooks).forEach(([key, hook]) => { + for (const key of Object.keys(parentMethod.hooks)) { + const hook = parentMethod.hooks[key]; + hooks[key] = { ...hook, after: Object.size(hook.after) > 0 ? new Set(hook.after) : undefined }; - }); + } } current[methodName] = {...parentMethod, watchers, hooks}; @@ -177,7 +198,9 @@ export function inheritParams(meta: ComponentMeta, parentMeta: ComponentMeta): v export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): void { const {mods} = meta; - Object.entries(parentMeta.mods).forEach(([modName, parentModValues]) => { + for (const modName of Object.keys(parentMeta.mods)) { + const parentModValues = parentMeta.mods[modName]; + const currentModValues = mods[modName], forkedParentModValues = parentModValues?.slice() ?? []; @@ -185,7 +208,7 @@ export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): voi if (currentModValues != null) { const values = Object.createDict(); - currentModValues.slice().forEach((modVal, i) => { + for (const [i, modVal] of currentModValues.slice().entries()) { if (modVal !== PARENT) { const modName = String(modVal); @@ -193,14 +216,14 @@ export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): voi values[modName] = >modVal; } - return; + continue; } const hasDefault = currentModValues.some((el) => Object.isArray(el)); let appliedDefault = !hasDefault; - forkedParentModValues.forEach((modVal) => { + for (const modVal of forkedParentModValues) { const modsName = String(modVal); if (!(modsName in values)) { @@ -211,10 +234,10 @@ export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): voi forkedParentModValues[i] = modVal[0]; appliedDefault = true; } - }); + } currentModValues.splice(i, 1, ...forkedParentModValues); - }); + } mods[modName] = Object .values(values) @@ -223,5 +246,5 @@ export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): voi } else if (!(modName in mods)) { mods[modName] = forkedParentModValues; } - }); + } } diff --git a/src/core/component/meta/interface/meta.ts b/src/core/component/meta/interface/meta.ts index 0f1fb0e631..aaf0a583e0 100644 --- a/src/core/component/meta/interface/meta.ts +++ b/src/core/component/meta/interface/meta.ts @@ -6,10 +6,12 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import type { WatchPath } from 'core/object/watch'; + import type { PropOptions } from 'core/component/decorators'; import type { RenderFunction, WritableComputedOptions } from 'core/component/engines'; -import type { ComponentConstructor, WatchObject, WatchPath, ModsDecl } from 'core/component/interface'; +import type { ComponentConstructor, WatchObject, ModsDecl } from 'core/component/interface'; import type { ComponentOptions } from 'core/component/meta/interface/options'; import type { @@ -157,7 +159,7 @@ export interface ComponentMeta { * A dictionary containing functions to initialize the component metaobject. * The keys in the dictionary are the component entities: props, fields, methods, etc. */ - metaInitializers: Dictionary<(meta: ComponentMeta) => void>; + metaInitializers: Map void>; /** * A less abstract representation of the component would typically include the following elements, diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index d8251b6cfc..4bf260303f 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -6,11 +6,13 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ +import type { WatchPath } from 'core/object/watch'; + import type { WritableComputedOptions, DirectiveBinding } from 'core/component/engines'; import type { PropOptions, InitFieldFn, MergeFieldFn, UniqueFieldFn } from 'core/component/decorators'; -import type { ComponentInterface, FieldWatcher, MethodWatcher, WatchPath, Hook } from 'core/component/interface'; +import type { ComponentInterface, FieldWatcher, MethodWatcher, Hook } from 'core/component/interface'; export interface ComponentProp extends PropOptions { forceUpdate: boolean; diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index 8d25cc1a81..307e630483 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -45,14 +45,17 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me const proto = constructor.prototype, - parentProto = Object.getPrototypeOf(proto), descriptors = Object.getOwnPropertyDescriptors(proto); - for (const [name, desc] of Object.entries(descriptors)) { + let parentProto: CanNull = null; + + for (const name of Object.keys(descriptors)) { if (name === 'constructor') { continue; } + const desc = descriptors[name]; + // Methods if ('value' in desc) { const method = desc.value; @@ -79,14 +82,16 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me component.methods[name] = wrapper; const - watchers = methodDesc.watchers != null ? Object.entries(methodDesc.watchers) : [], - hooks = methodDesc.hooks != null ? Object.entries(methodDesc.hooks) : []; + watchers = methodDesc.watchers != null ? Object.keys(methodDesc.watchers) : [], + hooks = methodDesc.hooks != null ? Object.keys(methodDesc.hooks) : []; if (watchers.length > 0 || hooks.length > 0) { - metaInitializers[name] = (meta) => { + metaInitializers.set(name, (meta) => { const isFunctional = meta.params.functional === true; - for (const [watcherName, watcher] of watchers) { + for (const watcherName of watchers) { + const watcher = methodDesc.watchers![watcherName]; + if (watcher == null || isFunctional && watcher.functional === false) { continue; } @@ -102,14 +107,16 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me }); } - for (const [hookName, hook] of hooks) { + for (const hookName of hooks) { + const hook = methodDesc.hooks![hookName]; + if (isFunctional && hook.functional === false) { continue; } meta.hooks[hookName].push({...hook, fn: method}); } - }; + }); } // Accessors @@ -156,7 +163,9 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me set = desc.set ?? old?.set, get = desc.get ?? old?.get; - if (name in parentProto) { + parentProto ??= Object.getPrototypeOf(proto); + + if (name in parentProto!) { // To use `super` within the setter, we also create a new method with a name `${key}Setter` if (set != null) { const methodName = `${name}Setter`; From ae2e78827d192ef922048f5b02d83d07d01a3f0b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 15:54:17 +0300 Subject: [PATCH 213/334] fix: better use init --- src/components/super/i-data/data.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index f0cd18ed26..bae2b5190e 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -155,8 +155,8 @@ abstract class iDataData extends iBlock implements iDataProvider { readonly checkDBEquality: CheckDBEquality = true; /** {@link iDataProvider.requestParams} */ - @system({merge: true}) - readonly requestParams: RequestParams = {get: {}}; + @system({merge: true, init: () => ({get: {}})}) + readonly requestParams!: RequestParams; /** * The raw component data from the data provider From ffdf93655e0030f3a92966bd4f727af43f818879 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 16:11:45 +0300 Subject: [PATCH 214/334] chore: optimization --- src/core/component/accessor/index.ts | 78 ++++++++++++++-------------- src/core/component/method/index.ts | 4 +- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index ffd6702805..1965bb05f6 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -78,7 +78,9 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const isFunctional = meta.params.functional === true; - Object.entries(meta.accessors).forEach(([name, accessor]) => { + for (const name of Object.keys(meta.accessors)) { + const accessor = meta.accessors[name]; + const tiedWith = tiedFields[name]; // In the `tiedFields` dictionary, @@ -101,23 +103,13 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { delete tiedFields[tiedWith]; } - return; + continue; } let getterInitialized = false; - Object.defineProperty(component, name, { - configurable: true, - enumerable: true, - get: accessor.get != null ? get : undefined, - set: accessor.set - }); - - function get(this: typeof component): unknown { - if (accessor == null) { - return; - } - + // eslint-disable-next-line func-style + const get = function get(this: typeof component): unknown { if (!getterInitialized) { getterInitialized = true; @@ -158,12 +150,21 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { } return accessor.get!.call(this); - } - }); + }; + + Object.defineProperty(component, name, { + configurable: true, + enumerable: true, + get: accessor.get != null ? get : undefined, + set: accessor.set + }); + } const cachedAccessors = new Set(); - Object.entries(meta.computedFields).forEach(([name, computed]) => { + for (const name of Object.keys(meta.computedFields)) { + const computed = meta.computedFields[name]; + const tiedWith = tiedFields[name]; // In the `tiedFields` dictionary, @@ -187,7 +188,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { delete tiedFields[tiedWith]; } - return; + continue; } const @@ -196,18 +197,8 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { let getterInitialized = canUseForeverCache; - Object.defineProperty(component, name, { - configurable: true, - enumerable: true, - get: computed.get != null ? get : undefined, - set: computed.set - }); - - function get(this: typeof component): unknown { - if (computed == null) { - return; - } - + // eslint-disable-next-line func-style + const get = function get(this: typeof component): unknown { if (!getterInitialized) { getterInitialized = true; @@ -283,7 +274,9 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { if (canUseCache && cacheStatus in get) { if (this.hook !== 'created') { - effects.forEach((applyEffect) => applyEffect()); + for (const applyEffect of effects) { + applyEffect(); + } } return get[cacheStatus]; @@ -297,25 +290,34 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { } return value; - } - }); + }; + + Object.defineProperty(component, name, { + configurable: true, + enumerable: true, + get: computed.get != null ? get : undefined, + set: computed.set + }); + } // Register a worker to clean up memory upon component destruction $destructors.push(() => { // eslint-disable-next-line require-yield gc.add(function* destructor() { - cachedAccessors.forEach((getter) => { + for (const getter of cachedAccessors) { delete getter[cacheStatus]; - }); + } cachedAccessors.clear(); }()); }); if (deprecatedProps != null) { - Object.entries(deprecatedProps).forEach(([name, renamedTo]) => { + for (const name of Object.keys(deprecatedProps)) { + const renamedTo = deprecatedProps[name]; + if (renamedTo == null) { - return; + continue; } Object.defineProperty(component, name, { @@ -331,7 +333,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { component[renamedTo] = val; } }); - }); + } } function fakeHandler() { diff --git a/src/core/component/method/index.ts b/src/core/component/method/index.ts index b78dbef3b1..90c6945a1a 100644 --- a/src/core/component/method/index.ts +++ b/src/core/component/method/index.ts @@ -22,7 +22,9 @@ export function attachMethodsFromMeta(component: ComponentInterface): void { const isFunctional = meta.params.functional === true; - for (const [name, method] of Object.entries(methods)) { + for (const name of Object.keys(methods)) { + const method = methods[name]; + if (method == null || !SSR && isFunctional && method.functional === false) { continue; } From 86636eeb8b8cdf6d639c3a71712a417cea6e665a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 17:01:44 +0300 Subject: [PATCH 215/334] chore: optimization --- .../component/decorators/system/decorator.ts | 76 ++++++++++++++- src/core/component/field/init.ts | 10 +- .../meta/{field/sort.ts => field.ts} | 13 +-- src/core/component/meta/field/index.ts | 95 ------------------- src/core/component/meta/fill.ts | 18 ++-- src/core/component/meta/inherit.ts | 13 ++- src/core/component/meta/interface/types.ts | 6 +- 7 files changed, 98 insertions(+), 133 deletions(-) rename src/core/component/meta/{field/sort.ts => field.ts} (83%) delete mode 100644 src/core/component/meta/field/index.ts diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index 3bb23a5973..8a96143d60 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -17,6 +17,8 @@ import type { PartDecorator } from 'core/component/decorators/interface'; import type { InitFieldFn, DecoratorSystem, DecoratorField } from 'core/component/decorators/system/interface'; +const INIT = Symbol('The field initializer'); + /** * Marks a class property as a system field. * Mutations to a system field never cause components to re-render. @@ -95,7 +97,7 @@ export function system( } } - const field: ComponentField = meta[type][fieldName] ?? { + let field: ComponentField = meta[type][fieldName] ?? { src: meta.componentName, meta: {} }; @@ -121,7 +123,7 @@ export function system( } } - meta[type][fieldName] = normalizeFunctionalParams({ + field = normalizeFunctionalParams({ ...field, ...params, @@ -134,10 +136,80 @@ export function system( } }, meta); + meta[type][fieldName] = field; + + if (field.init == null || !(INIT in field.init)) { + let getDefValue: CanNull = null; + + if (field.default !== undefined) { + getDefValue = () => field.default; + + } else if (meta.instance[fieldName] !== undefined) { + const val = meta.instance[fieldName]; + + if (Object.isPrimitive(val)) { + getDefValue = () => val; + + } else { + // To prevent linking to the same type of component for non-primitive values, + // it's important to clone the default value from the component constructor. + getDefValue = () => Object.fastClone(val); + } + } + + if (field.init != null) { + const customInit = field.init; + + field.init = (ctx, store) => { + const val = customInit(ctx, store); + + if (val === undefined && getDefValue != null) { + if (store[fieldName] === undefined) { + return getDefValue(); + } + + return undefined; + } + + return val; + }; + + } else if (getDefValue != null) { + field.init = (_, store) => { + if (store[fieldName] === undefined) { + return getDefValue!(); + } + + return undefined; + }; + } + } + + if (field.init != null) { + Object.defineProperty(field.init, INIT, {value: true}); + } + if (isStore.test(fieldName)) { const tiedWith = isStore.replace(fieldName); meta.tiedFields[fieldName] = tiedWith; meta.tiedFields[tiedWith] = fieldName; } + + if (watchers != null && watchers.size > 0) { + meta.metaInitializers.set(fieldName, (meta) => { + const isFunctional = meta.params.functional === true; + + for (const watcher of watchers!.values()) { + if (isFunctional && watcher.functional === false) { + continue; + } + + const watcherListeners = meta.watchers[fieldName] ?? []; + meta.watchers[fieldName] = watcherListeners; + + watcherListeners.push(watcher); + } + }); + } }); } diff --git a/src/core/component/field/init.ts b/src/core/component/field/init.ts index 0234ffd48b..4e91315b15 100644 --- a/src/core/component/field/init.ts +++ b/src/core/component/field/init.ts @@ -27,20 +27,20 @@ export function initFields( component ); - from.forEach(([name, init]) => { + for (const [name, field] of from) { const sourceVal = store[name]; - if (init == null) { + if (field?.init == null) { store[name] = sourceVal; - return; + continue; } unsafe.$activeField = name; - init(component, store); + store[name] = field.init(unsafe, store); unsafe.$activeField = undefined; - }); + } return store; } diff --git a/src/core/component/meta/field/sort.ts b/src/core/component/meta/field.ts similarity index 83% rename from src/core/component/meta/field/sort.ts rename to src/core/component/meta/field.ts index 1cabc3e046..2017d6add1 100644 --- a/src/core/component/meta/field/sort.ts +++ b/src/core/component/meta/field.ts @@ -50,18 +50,13 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar /** * Sorts the specified fields and returns an array that is ordered and ready for initialization - * * @param fields - * @param scope */ -export function sortFields( - fields: ComponentFieldInitializers, - scope: Dictionary -): ComponentFieldInitializers { - return fields.sort(([aName], [bName]) => { +export function sortFields(fields: Dictionary): ComponentFieldInitializers { + return Object.entries(fields).sort(([aName], [bName]) => { const - aWeight = getFieldWeight(scope[aName], scope), - bWeight = getFieldWeight(scope[bName], scope); + aWeight = getFieldWeight(fields[aName], fields), + bWeight = getFieldWeight(fields[bName], fields); return aWeight - bWeight; }); diff --git a/src/core/component/meta/field/index.ts b/src/core/component/meta/field/index.ts deleted file mode 100644 index 82a01e67b8..0000000000 --- a/src/core/component/meta/field/index.ts +++ /dev/null @@ -1,95 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import { sortFields } from 'core/component/meta/field/sort'; - -import type { ComponentInterface } from 'core/component/interface'; -import type { ComponentMeta } from 'core/component/meta/interface'; - -/** - * Adds initialization functions to the metaobject for the given component field type, and also registers watchers - * - * @param type - * @param meta - */ -export function addFieldsToMeta(type: 'fields' | 'systemFields', meta: ComponentMeta): void { - const {watchers} = meta; - - const isFunctional = meta.params.functional === true; - - const - fields = meta[type], - fieldInitializers = type === 'fields' ? meta.fieldInitializers : meta.systemFieldInitializers; - - for (const fieldName of Object.keys(fields)) { - const field = fields[fieldName]; - - if (field == null || !SSR && isFunctional && field.functional === false) { - continue; - } - - let - getValue: CanNull = null, - getDefValue: CanNull = null; - - if (field.default !== undefined) { - getDefValue = () => field.default; - - } else if (meta.instance[fieldName] !== undefined) { - const val = meta.instance[fieldName]; - - if (Object.isPrimitive(val)) { - getDefValue = () => val; - - } else { - // To prevent linking to the same type of component for non-primitive values, - // it's important to clone the default value from the component constructor. - getDefValue = () => Object.fastClone(val); - } - } - - if (field.init != null) { - getValue = (ctx: ComponentInterface, store: Dictionary) => { - const val = field.init!(ctx.unsafe, store); - - if (val === undefined && getDefValue != null) { - if (store[fieldName] === undefined) { - store[fieldName] = getDefValue(); - } - - } else { - store[fieldName] = val; - } - }; - - } else if (getDefValue != null) { - getValue = (_: ComponentInterface, store: Dictionary) => { - if (store[fieldName] === undefined) { - store[fieldName] = getDefValue!(); - } - }; - } - - fieldInitializers.push([fieldName, getValue]); - - if (field.watchers != null) { - for (const watcher of field.watchers.values()) { - if (isFunctional && watcher.functional === false) { - continue; - } - - const watcherListeners = watchers[fieldName] ?? []; - watchers[fieldName] = watcherListeners; - - watcherListeners.push(watcher); - } - } - } - - sortFields(fieldInitializers, fields); -} diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 9e137c74f6..0e531b8a39 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -8,7 +8,7 @@ import { isAbstractComponent } from 'core/component/reflect'; -import { addFieldsToMeta } from 'core/component/meta/field'; +import { sortFields } from 'core/component/meta/field'; import { addMethodsToMeta } from 'core/component/meta/method'; import type { ComponentConstructor, ModVal } from 'core/component/interface'; @@ -38,14 +38,12 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor Object.defineProperty(meta, BLUEPRINT, { value: { watchers: meta.watchers, - hooks: meta.hooks, - fieldInitializers: meta.fieldInitializers, - systemFieldInitializers: meta.systemFieldInitializers + hooks: meta.hooks } }); } - type Blueprint = Pick; + type Blueprint = Pick; const blueprint: CanNull = meta[BLUEPRINT]; @@ -58,23 +56,19 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor Object.assign(meta, { hooks, - watchers: {...blueprint.watchers}, - fieldInitializers: blueprint.fieldInitializers.slice(), - systemFieldInitializers: blueprint.systemFieldInitializers.slice() + watchers: {...blueprint.watchers} }); } const {component} = meta; - addFieldsToMeta('fields', meta); - addFieldsToMeta('systemFields', meta); + meta.fieldInitializers = sortFields(meta.fields); + meta.systemFieldInitializers = sortFields(meta.systemFields); for (const init of meta.metaInitializers.values()) { init(meta); } - // Modifiers - if (isFirstFill) { const {mods} = component; diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 9fa8eb48b3..b787fd0e8e 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -21,7 +21,7 @@ import type { ComponentMeta } from 'core/component/meta/interface'; export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): ComponentMeta { const decoratedKeys = componentDecoratedKeys[meta.componentName]; - Object.assign(meta.tiedFields, parentMeta.tiedFields); + meta.tiedFields = {...parentMeta.tiedFields}; if (parentMeta.metaInitializers.size > 0) { meta.metaInitializers = new Map(parentMeta.metaInitializers); @@ -34,18 +34,21 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com inheritParams(meta, parentMeta); inheritProp(meta.props, parentMeta.props); - Object.assign(meta.component.props, parentMeta.component.props); - Object.assign(meta.component.attrs, parentMeta.component.attrs); + + meta.component.props = {...parentMeta.component.props}; + meta.component.attrs = {...parentMeta.component.attrs}; inheritField(meta.fields, parentMeta.fields); inheritField(meta.systemFields, parentMeta.systemFields); inheritAccessors(meta.accessors, parentMeta.accessors); inheritAccessors(meta.computedFields, parentMeta.computedFields); - Object.assign(meta.component.computed, parentMeta.component.computed); + + meta.component.computed = {...parentMeta.component.computed}; inheritMethods(meta.methods, parentMeta.methods); - Object.assign(meta.component.methods, parentMeta.component.methods); + + meta.component.methods = {...parentMeta.component.methods}; if (meta.params.partial == null) { inheritMods(meta, parentMeta); diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index 4bf260303f..d638068660 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -47,11 +47,7 @@ export interface ComponentField]>; +export type ComponentFieldInitializers = Array<[string, CanUndef]>; export type ComponentAccessorCacheType = boolean | From 621dba8213bdf7857ebebb36bb488b2ae92f2c4c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 17:28:16 +0300 Subject: [PATCH 216/334] chore: optimization --- .../component/functional/context/create.ts | 17 ++++++++----- src/core/component/hook/index.ts | 19 +++++++-------- src/core/component/init/interface.ts | 1 - .../component/init/states/before-create.ts | 4 ---- src/core/component/meta/interface/types.ts | 1 - src/core/component/method/README.md | 4 ---- src/core/component/method/index.ts | 24 ------------------- src/core/component/prop/init.ts | 8 ++++--- 8 files changed, 25 insertions(+), 53 deletions(-) diff --git a/src/core/component/functional/context/create.ts b/src/core/component/functional/context/create.ts index 1a67d35ab7..c1b2033274 100644 --- a/src/core/component/functional/context/create.ts +++ b/src/core/component/functional/context/create.ts @@ -41,8 +41,10 @@ export function createVirtualContext( const handlers: Array<[string, boolean, Function]> = []; if (props != null) { - Object.entries(props).forEach(([name, prop]) => { - const normalizedName = name.camelize(false); + for (const name of Object.keys(props)) { + const + prop = props[name], + normalizedName = name.camelize(false); if (normalizedName in meta.props) { $props[normalizedName] = prop; @@ -62,7 +64,7 @@ export function createVirtualContext( $attrs[name] = prop; } - }); + } } let $options: {directives: Dictionary; components: Dictionary}; @@ -86,8 +88,12 @@ export function createVirtualContext( } const virtualCtx = Object.cast({ + __proto__: meta.component.methods, + componentName: meta.componentName, + render: meta.component.render, + meta, get instance(): typeof meta['instance'] { @@ -135,18 +141,17 @@ export function createVirtualContext( }); init.beforeCreateState(virtualCtx, meta, { - addMethods: true, implementEventAPI: true }); - handlers.forEach(([event, once, handler]) => { + for (const [event, once, handler] of handlers) { if (once) { unsafe.$once(event, handler); } else { unsafe.$on(event, handler); } - }); + } init.beforeDataCreateState(virtualCtx); return initDynamicComponentLifeCycle(virtualCtx); diff --git a/src/core/component/hook/index.ts b/src/core/component/hook/index.ts index 4cc02220f1..57d9967b5c 100644 --- a/src/core/component/hook/index.ts +++ b/src/core/component/hook/index.ts @@ -80,8 +80,8 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn if (hooks.some((hook) => hook.after != null && hook.after.size > 0)) { const emitter = new QueueEmitter(); - hooks.forEach((hook, i) => { - const nm = hook.name; + for (const [i, hook] of hooks.entries()) { + const hookName = hook.name; if (hook.once) { toDelete ??= []; @@ -92,16 +92,16 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn const res = args.length > 0 ? hook.fn.apply(component, args) : hook.fn.call(component); if (Object.isPromise(res)) { - return res.then(() => nm != null ? emitter.emit(nm) : undefined); + return res.then(() => hookName != null ? emitter.emit(hookName) : undefined); } - const tasks = nm != null ? emitter.emit(nm) : null; + const tasks = hookName != null ? emitter.emit(hookName) : null; if (tasks != null) { return tasks; } }); - }); + } removeFromHooks(toDelete); @@ -114,7 +114,7 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn } else { let tasks: CanNull>> = null; - hooks.forEach((hook, i) => { + for (const [i, hook] of hooks.entries()) { let res: unknown; switch (args.length) { @@ -139,11 +139,10 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn tasks ??= []; tasks.push(res); } - }); + } removeFromHooks(toDelete); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (tasks != null) { return Promise.all(tasks).then(() => undefined); } @@ -155,9 +154,9 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn function removeFromHooks(toDelete: CanNull) { if (toDelete != null) { - toDelete.reverse().forEach((i) => { + for (const i of toDelete.reverse()) { hooks.splice(i, 1); - }); + } } } } diff --git a/src/core/component/init/interface.ts b/src/core/component/init/interface.ts index 0d321517d8..308aa3d0e0 100644 --- a/src/core/component/init/interface.ts +++ b/src/core/component/init/interface.ts @@ -7,6 +7,5 @@ */ export interface InitBeforeCreateStateOptions { - addMethods?: boolean; implementEventAPI?: boolean; } diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 049fc18feb..42f05416a8 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -249,10 +249,6 @@ export function beforeCreateState( }()); }); - if (opts?.addMethods) { - attachMethodsFromMeta(component); - } - if (opts?.implementEventAPI) { implementEventEmitterAPI(component); } diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index d638068660..7c5e6f3536 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -70,7 +70,6 @@ export interface ComponentMethod { src?: string; wrapper?: boolean; - functional?: boolean; watchers?: Dictionary; hooks?: ComponentMethodHooks; diff --git a/src/core/component/method/README.md b/src/core/component/method/README.md index f33a0c4596..29e3f23a48 100644 --- a/src/core/component/method/README.md +++ b/src/core/component/method/README.md @@ -4,10 +4,6 @@ This module offers an API for initializing component methods on a component inst ## Functions -### attachMethodsFromMeta - -This function attaches methods to the passed component instance, taken from its associated metaobject. - ### callMethodFromComponent This function invokes a specific method from the passed component instance. diff --git a/src/core/component/method/index.ts b/src/core/component/method/index.ts index 90c6945a1a..564203442b 100644 --- a/src/core/component/method/index.ts +++ b/src/core/component/method/index.ts @@ -13,30 +13,6 @@ import type { ComponentInterface } from 'core/component/interface'; -/** - * Attaches methods to the passed component instance, taken from its associated metaobject - * @param component - */ -export function attachMethodsFromMeta(component: ComponentInterface): void { - const {meta, meta: {methods}} = component.unsafe; - - const isFunctional = meta.params.functional === true; - - for (const name of Object.keys(methods)) { - const method = methods[name]; - - if (method == null || !SSR && isFunctional && method.functional === false) { - continue; - } - - component[name] = method.fn.bind(component); - } - - if (isFunctional) { - component.render = Object.cast(meta.component.render); - } -} - /** * Invokes a specific method from the passed component instance * diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index 561d2207a5..234ffe21f4 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -44,13 +44,15 @@ export function initProps( isFunctional = meta.params.functional === true, source: typeof props = p.forceUpdate ? props : attrs; - Object.entries(source).forEach(([propName, prop]) => { + for (const propName of Object.keys(source)) { + const prop = source[propName]; + const canSkip = prop == null || !SSR && isFunctional && prop.functional === false; if (canSkip) { - return; + continue; } unsafe.$activeField = propName; @@ -111,7 +113,7 @@ export function initProps( get: () => opts.forceUpdate ? propValue : store[privateField] }); } - }); + } unsafe.$activeField = undefined; return store; From fee0eaf2872fb2205ba8fadb18b05a7691c8bcc2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 9 Oct 2024 18:25:57 +0300 Subject: [PATCH 217/334] chore: optimization --- .../component/directives/attrs/helpers.ts | 6 ++-- src/core/component/directives/attrs/index.ts | 31 +++++++++++-------- src/core/component/directives/render/index.ts | 7 ++--- src/core/component/event/component.ts | 6 ++-- .../component/functional/context/inherit.ts | 8 +++-- src/core/component/gc/const.ts | 4 +-- src/core/component/init/component.ts | 10 ++++-- .../component/init/states/before-create.ts | 6 ++-- .../component/init/states/before-destroy.ts | 5 ++- src/core/component/meta/field.ts | 4 +-- src/core/component/prop/helpers.ts | 4 +-- src/core/component/render/helpers/attrs.ts | 18 ++++++----- src/core/component/render/helpers/flags.ts | 4 +-- 13 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/core/component/directives/attrs/helpers.ts b/src/core/component/directives/attrs/helpers.ts index d14ee8cd86..adc69f4248 100644 --- a/src/core/component/directives/attrs/helpers.ts +++ b/src/core/component/directives/attrs/helpers.ts @@ -64,13 +64,13 @@ export function normalizePropertyAttribute(name: string): string { export function normalizeDirectiveModifiers(rawModifiers: string): Record { const modifiers = {}; - rawModifiers.split('.').forEach((modifier) => { - modifier = modifier.trim(); + for (const rawModifier of rawModifiers.split('.')) { + const modifier = rawModifier.trim(); if (modifier !== '') { modifiers[modifier] = true; } - }); + } return modifiers; } diff --git a/src/core/component/directives/attrs/index.ts b/src/core/component/directives/attrs/index.ts index fb7bd444e5..e4299adf07 100644 --- a/src/core/component/directives/attrs/index.ts +++ b/src/core/component/directives/attrs/index.ts @@ -139,9 +139,9 @@ ComponentEngine.directive('attrs', { case 'on': { if (Object.isDictionary(value)) { - Object.entries(value).forEach(([name, handler]) => { - attachEvent(name, handler); - }); + for (const name of Object.keys(value)) { + attachEvent(name, value[name]); + } } return; @@ -149,10 +149,10 @@ ComponentEngine.directive('attrs', { case 'bind': { if (Object.isDictionary(value)) { - Object.entries(value).forEach(([name, val]) => { - attrs[name] = val; + for (const name of Object.keys(value)) { + attrs[name] = value[name]; attrsKeys.push(name); - }); + } } return; @@ -266,10 +266,13 @@ ComponentEngine.directive('attrs', { eventChunks = event.split('.'), flags = Object.createDict(); - // The first element is the event name; we need to slice only the part containing the event modifiers - eventChunks.slice(1).forEach((chunk) => flags[chunk] = true); event = eventChunks[0]; + // The first element is the event name; we need to slice only the part containing the event modifiers + for (const chunk of eventChunks.slice(1)) { + flags[chunk] = true; + } + if (flags.right && !event.startsWith('key')) { event = 'onContextmenu'; delete flags.right; @@ -363,14 +366,16 @@ ComponentEngine.directive('attrs', { attrs = normalizeComponentAttrs(attrs, null, componentMeta)!; } - Object.entries(attrs).forEach(([name, value]) => { + for (const name of Object.keys(attrs)) { + const value = attrs[name]; + if (name.startsWith('v-')) { parseDirective(name, value); } else if (!name.startsWith('@')) { patchProps(props, normalizePropertyAttribute(name), value); } - }); + } return props; @@ -396,9 +401,9 @@ ComponentEngine.directive('attrs', { case 'bind': { if (Object.isDictionary(attrVal)) { - Object.entries(attrVal).forEach(([name, val]) => { - props[name] = val; - }); + for (const name of Object.keys(attrVal)) { + props[name] = attrVal[name]; + } } return; diff --git a/src/core/component/directives/render/index.ts b/src/core/component/directives/render/index.ts index 66e720f211..50e4889f95 100644 --- a/src/core/component/directives/render/index.ts +++ b/src/core/component/directives/render/index.ts @@ -73,14 +73,13 @@ ComponentEngine.directive('render', { } else { if (Object.isArray(newVNode)) { if (isSlot(newVNode[0])) { - newVNode.forEach((vnode) => { - const - slot = vnode.props?.slot; + for (const vnode of newVNode) { + const slot = vnode.props?.slot; if (slot != null) { slots[slot] = () => vnode.children ?? getDefaultSlotFromChildren(slot); } - }); + } return; } diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index 8af67569bb..8c23930060 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -152,7 +152,9 @@ export function implementEventEmitterAPI(component: object): void { emitter = Object.cast(opts.rawEmitter ? reversedEmitter : wrappedReversedEmitter); } - Array.toArray(event).forEach((event) => { + const events = Array.toArray(event); + + for (const event of events) { if (method === 'off' && cb == null) { emitter.removeAllListeners(event); @@ -163,7 +165,7 @@ export function implementEventEmitterAPI(component: object): void { links.push(Object.cast(opts.rawEmitter ? cb : link)); } } - }); + } if (isOnLike) { return Object.isArray(event) ? links : links[0]; diff --git a/src/core/component/functional/context/inherit.ts b/src/core/component/functional/context/inherit.ts index aabe9883e3..8d9a52eb12 100644 --- a/src/core/component/functional/context/inherit.ts +++ b/src/core/component/functional/context/inherit.ts @@ -56,9 +56,11 @@ export function inheritContext( ]; fields.forEach((cluster) => { - Object.entries(cluster).forEach(([name, field]) => { + for (const name of Object.keys(cluster)) { + const field = cluster[name]; + if (field == null) { - return; + continue; } const link = linkedFields[name]; @@ -106,6 +108,6 @@ export function inheritContext( ctx[name] = parentCtx[name]; } } - }); + } }); } diff --git a/src/core/component/gc/const.ts b/src/core/component/gc/const.ts index d68742b5b7..b5ac2025c9 100644 --- a/src/core/component/gc/const.ts +++ b/src/core/component/gc/const.ts @@ -31,9 +31,9 @@ export const add = (task: Iterator): number => { const l = queue.push(task); if (newTaskHandlersQueue.length > 0) { - newTaskHandlersQueue.splice(0, newTaskHandlersQueue.length).forEach((handler) => { + for (const handler of newTaskHandlersQueue.splice(0, newTaskHandlersQueue.length)) { handler(); - }); + } } return l; diff --git a/src/core/component/init/component.ts b/src/core/component/init/component.ts index fae4173e5f..2a852beb95 100644 --- a/src/core/component/init/component.ts +++ b/src/core/component/init/component.ts @@ -46,7 +46,10 @@ export function registerParentComponents(component: ComponentConstructorInfo): b const regParentComponent = componentRegInitializers[parentName]; if (regParentComponent != null) { - regParentComponent.forEach((reg) => reg()); + for (const reg of regParentComponent) { + reg(); + } + delete componentRegInitializers[parentName]; return true; } @@ -73,7 +76,10 @@ export function registerComponent(name: CanUndef): CanNull reg()); + for (const reg of regComponent) { + reg(); + } + delete componentRegInitializers[name]; } diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 42f05416a8..d5bd4b2a46 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -20,7 +20,7 @@ import { getNormalParent } from 'core/component/traverse'; import { initProps, attachAttrPropsListeners } from 'core/component/prop'; import { initFields } from 'core/component/field'; import { attachAccessorsFromMeta } from 'core/component/accessor'; -import { attachMethodsFromMeta, callMethodFromComponent } from 'core/component/method'; +import { callMethodFromComponent } from 'core/component/method'; import { runHook } from 'core/component/hook'; import { implementEventEmitterAPI } from 'core/component/event'; @@ -205,7 +205,7 @@ export function beforeCreateState( unsafe.$normalParent = getNormalParent(component); - ['$root', '$parent', '$normalParent'].forEach((key) => { + for (const key of ['$root', '$parent', '$normalParent']) { const val = unsafe[key]; if (val != null) { @@ -216,7 +216,7 @@ export function beforeCreateState( value: getComponentContext(Object.cast(val)) }); } - }); + } Object.defineProperty(unsafe, '$children', { configurable: true, diff --git a/src/core/component/init/states/before-destroy.ts b/src/core/component/init/states/before-destroy.ts index 76513140a2..9f6f803638 100644 --- a/src/core/component/init/states/before-destroy.ts +++ b/src/core/component/init/states/before-destroy.ts @@ -40,7 +40,10 @@ export function beforeDestroyState(component: ComponentInterface, opts: Componen unsafe.async.clearAll().locked = true; unsafe.$async.clearAll().locked = true; - unsafe.$destructors.forEach((destructor) => destructor()); + + for (const destructor of unsafe.$destructors) { + destructor(); + } if ($el != null && $el.component === component) { delete $el.component; diff --git a/src/core/component/meta/field.ts b/src/core/component/meta/field.ts index 2017d6add1..1643c3d9f9 100644 --- a/src/core/component/meta/field.ts +++ b/src/core/component/meta/field.ts @@ -30,7 +30,7 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar if (after != null) { weight += after.size; - after.forEach((name) => { + for (const name of after) { const dep = scope[name]; if (dep == null) { @@ -38,7 +38,7 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar } weight += getFieldWeight(dep, scope); - }); + } } if (!field.atom) { diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index 3d675c53ea..83cfd08a32 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -79,13 +79,13 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { await parent.$nextTick(); } - propValuesToUpdate.forEach(([propName, getterName]) => { + for (const [propName, getterName] of propValuesToUpdate) { const getter = unsafe.$attrs[getterName]; if (Object.isFunction(getter)) { unsafe[`[[${propName}]]`] = getter()[0]; } - }); + } } } } diff --git a/src/core/component/render/helpers/attrs.ts b/src/core/component/render/helpers/attrs.ts index 61bbfc9f22..4e37b19a06 100644 --- a/src/core/component/render/helpers/attrs.ts +++ b/src/core/component/render/helpers/attrs.ts @@ -59,7 +59,9 @@ export function resolveAttrs(this: ComponentInterface, vnode: T } if (Object.isArray(children)) { - children.forEach((child) => resolveAttrs.call(this, Object.cast(child))); + for (const child of children) { + resolveAttrs.call(this, Object.cast(child)); + } } if (Object.isArray(dynamicChildren) && dynamicChildren.length > 0) { @@ -139,16 +141,16 @@ export function resolveAttrs(this: ComponentInterface, vnode: T const dynamicProps = vnode.dynamicProps ?? []; vnode.dynamicProps = dynamicProps; - Object.keys(props).forEach((prop) => { + for (const prop of Object.keys(props)) { if (isHandler.test(prop)) { if (SSR) { - delete props![prop]; + delete props[prop]; } else { dynamicProps.push(prop); } } - }); + } } delete props[key]; @@ -173,15 +175,15 @@ export function resolveAttrs(this: ComponentInterface, vnode: T names = props[key]; if (names != null) { - names.split(' ').forEach((name: string) => { + for (const name of names.split(' ')) { if ('classes' in this && this.classes?.[name] != null) { - Object.assign(props, mergeProps({class: props?.class}, {class: this.classes[name]})); + Object.assign(props, mergeProps({class: props.class}, {class: this.classes[name]})); } if ('styles' in this && this.styles?.[name] != null) { - Object.assign(props, mergeProps({style: props?.style}, {style: this.styles[name]})); + Object.assign(props, mergeProps({style: props.style}, {style: this.styles[name]})); } - }); + } delete props[key]; } diff --git a/src/core/component/render/helpers/flags.ts b/src/core/component/render/helpers/flags.ts index bab6c549c0..76d89d903e 100644 --- a/src/core/component/render/helpers/flags.ts +++ b/src/core/component/render/helpers/flags.ts @@ -46,7 +46,7 @@ type PatchFlags = Exclude; * ``` */ export function setVNodePatchFlags(vnode: VNode, ...flags: Flags): void { - flags.forEach((flag) => { + for (const flag of flags) { const val = flagValues[flag], dest = flagDest[flag]; @@ -55,7 +55,7 @@ export function setVNodePatchFlags(vnode: VNode, ...flags: Flags): void { if ((vnode[dest] & val) === 0) { vnode[dest] += val; } - }); + } } /** From 57c287fa09a0b2ae0a4b0d83c2840a415d9563d6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 10 Oct 2024 15:36:59 +0300 Subject: [PATCH 218/334] chore: refactoring & optimization --- src/core/component/accessor/index.ts | 16 +++- src/core/component/directives/render/index.ts | 6 +- src/core/component/engines/vue3/lib.ts | 16 ++-- src/core/component/engines/vue3/render.ts | 27 +++--- src/core/component/field/init.ts | 4 +- .../component/functional/context/create.ts | 11 +-- .../component/functional/context/inherit.ts | 43 +++++---- src/core/component/hook/index.ts | 10 ++- .../component/init/states/before-destroy.ts | 4 +- src/core/component/meta/field.ts | 4 +- src/core/component/meta/fill.ts | 17 +++- src/core/component/meta/fork.ts | 13 ++- src/core/component/meta/inherit.ts | 50 ++++++++--- src/core/component/meta/interface/meta.ts | 8 +- src/core/component/meta/method.ts | 7 +- src/core/component/prop/init.ts | 8 +- src/core/component/queue-emitter/index.ts | 18 ++-- src/core/component/reflect/mod.ts | 45 +++++----- src/core/component/render/helpers/attrs.ts | 22 +++-- src/core/component/render/helpers/flags.ts | 4 +- .../component/render/helpers/normalizers.ts | 60 ++++++++----- src/core/component/render/helpers/props.ts | 8 +- src/core/component/render/wrappers.ts | 33 ++++--- src/core/component/watch/bind.ts | 54 ++++++----- src/core/component/watch/component-api.ts | 89 ++++++++++--------- src/core/component/watch/create.ts | 25 ++++-- src/core/component/watch/helpers.ts | 9 +- 27 files changed, 377 insertions(+), 234 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index 1965bb05f6..c5ea698f4c 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -78,7 +78,11 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const isFunctional = meta.params.functional === true; - for (const name of Object.keys(meta.accessors)) { + const accessorsNames = Object.keys(meta.accessors); + + for (let i = 0; i < accessorsNames.length; i++) { + const name = accessorsNames[i]; + const accessor = meta.accessors[name]; const tiedWith = tiedFields[name]; @@ -162,7 +166,11 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const cachedAccessors = new Set(); - for (const name of Object.keys(meta.computedFields)) { + const computedNames = Object.keys(meta.computedFields); + + for (let i = 0; i < computedNames.length; i++) { + const name = computedNames[i]; + const computed = meta.computedFields[name]; const tiedWith = tiedFields[name]; @@ -274,8 +282,8 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { if (canUseCache && cacheStatus in get) { if (this.hook !== 'created') { - for (const applyEffect of effects) { - applyEffect(); + for (let i = 0; i < effects.length; i++) { + effects[i](); } } diff --git a/src/core/component/directives/render/index.ts b/src/core/component/directives/render/index.ts index 50e4889f95..5ab9106407 100644 --- a/src/core/component/directives/render/index.ts +++ b/src/core/component/directives/render/index.ts @@ -73,8 +73,10 @@ ComponentEngine.directive('render', { } else { if (Object.isArray(newVNode)) { if (isSlot(newVNode[0])) { - for (const vnode of newVNode) { - const slot = vnode.props?.slot; + for (let i = 0; i < newVNode.length; i++) { + const + vnode = newVNode[i], + slot = vnode.props?.slot; if (slot != null) { slots[slot] = () => vnode.children ?? getDefaultSlotFromChildren(slot); diff --git a/src/core/component/engines/vue3/lib.ts b/src/core/component/engines/vue3/lib.ts index 5b4856a870..e5a0cffcd4 100644 --- a/src/core/component/engines/vue3/lib.ts +++ b/src/core/component/engines/vue3/lib.ts @@ -65,9 +65,9 @@ const Vue = makeLazy( call: { component: (contexts, ...args) => { if (args.length === 1) { - contexts.forEach((ctx) => { + for (const ctx of contexts) { ctx.component.apply(ctx, Object.cast(args)); - }); + } return; } @@ -79,9 +79,9 @@ const Vue = makeLazy( directive: (contexts, ...args: any[]) => { if (args.length === 1) { - contexts.forEach((ctx) => { + for (const ctx of contexts) { ctx.directive.apply(ctx, Object.cast(args)); - }); + } return; } @@ -92,15 +92,15 @@ const Vue = makeLazy( }, mixin: (contexts, ...args) => { - contexts.forEach((ctx) => { + for (const ctx of contexts) { ctx.mixin.apply(ctx, Object.cast(args)); - }); + } }, provide: (contexts, ...args) => { - contexts.forEach((ctx) => { + for (const ctx of contexts) { ctx.provide.apply(ctx, Object.cast(args)); - }); + } } } } diff --git a/src/core/component/engines/vue3/render.ts b/src/core/component/engines/vue3/render.ts index 26f567870d..b82c2e0616 100644 --- a/src/core/component/engines/vue3/render.ts +++ b/src/core/component/engines/vue3/render.ts @@ -243,8 +243,8 @@ export function render(vnode: CanArray, parent?: ComponentInterface, grou gc.add(function* destructor() { const vnodes = Array.toArray(vnode); - for (const vnode of vnodes) { - destroy(vnode); + for (let i = 0; i < vnodes.length; i++) { + destroy(vnodes[i]); yield; } @@ -322,7 +322,10 @@ export function destroy(node: VNode | Node): void { } if (Object.isArray(vnode)) { - vnode.forEach(removeVNode); + for (let i = 0; i < vnode.length; i++) { + removeVNode(vnode[i]); + } + return; } @@ -333,11 +336,15 @@ export function destroy(node: VNode | Node): void { destroyedVNodes.add(vnode); if (Object.isArray(vnode.children)) { - vnode.children.forEach(removeVNode); + for (let i = 0; i < vnode.children.length; i++) { + removeVNode(vnode.children[i]); + } } - if (Object.isArray(vnode['dynamicChildren'])) { - vnode['dynamicChildren'].forEach((vnode) => removeVNode(Object.cast(vnode))); + if ('dynamicChildren' in vnode && Object.isArray(vnode.dynamicChildren)) { + for (let i = 0; i < vnode.dynamicChildren.length; i++) { + removeVNode(vnode.dynamicChildren[i]); + } } gc.add(function* destructor() { @@ -350,13 +357,13 @@ export function destroy(node: VNode | Node): void { yield; - ['dirs', 'children', 'dynamicChildren', 'dynamicProps'].forEach((key) => { + for (const key of ['dirs', 'children', 'dynamicChildren', 'dynamicProps']) { vnode[key] = []; - }); + } - ['el', 'ctx', 'ref', 'virtualComponent', 'virtualContext'].forEach((key) => { + for (const key of ['el', 'ctx', 'ref', 'virtualComponent', 'virtualContext']) { vnode[key] = null; - }); + } }()); } } diff --git a/src/core/component/field/init.ts b/src/core/component/field/init.ts index 4e91315b15..1663480b41 100644 --- a/src/core/component/field/init.ts +++ b/src/core/component/field/init.ts @@ -27,7 +27,9 @@ export function initFields( component ); - for (const [name, field] of from) { + for (let i = 0; i < from.length; i++) { + const [name, field] = from[i]; + const sourceVal = store[name]; if (field?.init == null) { diff --git a/src/core/component/functional/context/create.ts b/src/core/component/functional/context/create.ts index c1b2033274..b6b4001759 100644 --- a/src/core/component/functional/context/create.ts +++ b/src/core/component/functional/context/create.ts @@ -41,7 +41,11 @@ export function createVirtualContext( const handlers: Array<[string, boolean, Function]> = []; if (props != null) { - for (const name of Object.keys(props)) { + const keys = Object.keys(props); + + for (let i = 0; i < keys.length; i++) { + const name = keys[i]; + const prop = props[name], normalizedName = name.camelize(false); @@ -70,10 +74,7 @@ export function createVirtualContext( let $options: {directives: Dictionary; components: Dictionary}; if ('$options' in parent) { - const { - directives = {}, - components = {} - } = parent.$options; + const {directives = {}, components = {}} = parent.$options; $options = { directives: Object.create(directives), diff --git a/src/core/component/functional/context/inherit.ts b/src/core/component/functional/context/inherit.ts index 8d9a52eb12..9046a55274 100644 --- a/src/core/component/functional/context/inherit.ts +++ b/src/core/component/functional/context/inherit.ts @@ -31,33 +31,38 @@ export function inheritContext( // Additionally, we should not unmount the vnodes created within the component. parentCtx.$destroy({recursive: false, shouldUnmountVNodes: false}); + const linkedFields = {}; + const props = ctx.$props, parentProps = parentCtx.$props, - linkedFields = {}; + parentKeys = Object.keys(parentProps); - Object.keys(parentProps).forEach((prop) => { - const linked = parentCtx.$syncLinkCache.get(prop); + for (let i = 0; i < parentKeys.length; i++) { + const + prop = parentKeys[i], + linked = parentCtx.$syncLinkCache.get(prop); - if (linked == null) { - return; - } + if (linked != null) { + const links = Object.values(linked); - Object.values(linked).forEach((link) => { - if (link != null) { - linkedFields[link.path] = prop; + for (let i = 0; i < links.length; i++) { + const link = links[i]; + + if (link != null) { + linkedFields[link.path] = prop; + } } - }); - }); + } + } - const fields = [ - parentCtx.meta.systemFields, - parentCtx.meta.fields - ]; + for (const cluster of [parentCtx.meta.systemFields, parentCtx.meta.fields]) { + const keys = Object.keys(cluster); - fields.forEach((cluster) => { - for (const name of Object.keys(cluster)) { - const field = cluster[name]; + for (let i = 0; i < keys.length; i++) { + const + name = keys[i], + field = cluster[name]; if (field == null) { continue; @@ -109,5 +114,5 @@ export function inheritContext( } } } - }); + } } diff --git a/src/core/component/hook/index.ts b/src/core/component/hook/index.ts index 57d9967b5c..2f679371b4 100644 --- a/src/core/component/hook/index.ts +++ b/src/core/component/hook/index.ts @@ -80,8 +80,10 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn if (hooks.some((hook) => hook.after != null && hook.after.size > 0)) { const emitter = new QueueEmitter(); - for (const [i, hook] of hooks.entries()) { - const hookName = hook.name; + for (let i = 0; i < hooks.length; i++) { + const + hook = hooks[i], + hookName = hook.name; if (hook.once) { toDelete ??= []; @@ -114,7 +116,9 @@ export function runHook(hook: Hook, component: ComponentInterface, ...args: unkn } else { let tasks: CanNull>> = null; - for (const [i, hook] of hooks.entries()) { + for (let i = 0; i < hooks.length; i++) { + const hook = hooks[i]; + let res: unknown; switch (args.length) { diff --git a/src/core/component/init/states/before-destroy.ts b/src/core/component/init/states/before-destroy.ts index 9f6f803638..86f3dfe7a2 100644 --- a/src/core/component/init/states/before-destroy.ts +++ b/src/core/component/init/states/before-destroy.ts @@ -41,8 +41,8 @@ export function beforeDestroyState(component: ComponentInterface, opts: Componen unsafe.async.clearAll().locked = true; unsafe.$async.clearAll().locked = true; - for (const destructor of unsafe.$destructors) { - destructor(); + for (let i = 0; i < unsafe.$destructors.length; i++) { + unsafe.$destructors[i](); } if ($el != null && $el.component === component) { diff --git a/src/core/component/meta/field.ts b/src/core/component/meta/field.ts index 1643c3d9f9..3d2621b2a9 100644 --- a/src/core/component/meta/field.ts +++ b/src/core/component/meta/field.ts @@ -23,10 +23,10 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar return 0; } - const {after} = field; - let weight = 0; + const {after} = field; + if (after != null) { weight += after.size; diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 0e531b8a39..8b0eabdbb0 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -50,7 +50,10 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor if (blueprint != null) { const hooks = {}; - for (const name of Object.keys(blueprint.hooks)) { + const hookNames = Object.keys(blueprint.hooks); + + for (let i = 0; i < hookNames.length; i++) { + const name = hookNames[i]; hooks[name] = blueprint.hooks[name].slice(); } @@ -72,13 +75,19 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor if (isFirstFill) { const {mods} = component; - for (const modName of Object.keys(meta.mods)) { - const mod = meta.mods[modName]; + const modNames = Object.keys(meta.mods); + + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + mod = meta.mods[modName]; let defaultValue: CanUndef; if (mod != null) { - for (const val of mod) { + for (let i = 0; i < mod.length; i++) { + const val = mod[i]; + if (Object.isArray(val)) { defaultValue = val; break; diff --git a/src/core/component/meta/fork.ts b/src/core/component/meta/fork.ts index 6fd1a9b57c..3bcf6f0fc2 100644 --- a/src/core/component/meta/fork.ts +++ b/src/core/component/meta/fork.ts @@ -21,14 +21,21 @@ export function forkMeta(base: ComponentMeta): ComponentMeta { meta.tiedFields = {...meta.tiedFields}; meta.hooks = {}; - for (const name of Object.keys(base.hooks)) { + const hookNames = Object.keys(base.hooks); + + for (let i = 0; i < hookNames.length; i++) { + const name = hookNames[i]; meta.hooks[name] = base.hooks[name].slice(); } meta.watchers = {}; - for (const name of Object.keys(base.watchers)) { - const watchers = base.watchers[name]; + const watcherNames = Object.keys(base.watchers); + + for (let i = 0; i < watcherNames.length; i++) { + const + name = hookNames[i], + watchers = base.watchers[name]; if (watchers != null) { meta.watchers[name] = watchers.slice(); diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index b787fd0e8e..95242914c6 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -57,8 +57,12 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com return meta; function inheritProp(current: ComponentMeta['props'], parent: ComponentMeta['props']) { - for (const propName of Object.keys(parent)) { - const parentProp = parent[propName]; + const keys = Object.keys(parent); + + for (let i = 0; i < keys.length; i++) { + const + propName = keys[i], + parentProp = parent[propName]; if (parentProp == null) { continue; @@ -83,8 +87,12 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com } function inheritField(current: ComponentMeta['fields'], parent: ComponentMeta['fields']) { - for (const fieldName of Object.keys(parent)) { - const parentField = parent[fieldName]; + const keys = Object.keys(parent); + + for (let i = 0; i < keys.length; i++) { + const + fieldName = keys[i], + parentField = parent[fieldName]; if (parentField == null) { continue; @@ -118,14 +126,21 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com } function inheritAccessors(current: ComponentMeta['accessors'], parent: ComponentMeta['accessors']) { - for (const accessorName of Object.keys(parent)) { + const keys = Object.keys(parent); + + for (let i = 0; i < keys.length; i++) { + const accessorName = keys[i]; current[accessorName] = {...parent[accessorName]!}; } } function inheritMethods(current: ComponentMeta['methods'], parent: ComponentMeta['methods']) { - for (const methodName of Object.keys(parent)) { - const parentMethod = parent[methodName]; + const keys = Object.keys(parent); + + for (let i = 0; i < keys.length; i++) { + const + methodName = keys[i], + parentMethod = parent[methodName]; if (parentMethod == null) { continue; @@ -141,14 +156,21 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com hooks = {}; if (parentMethod.watchers != null) { - for (const key of Object.keys(parentMethod.watchers)) { + const keys = Object.keys(parentMethod.watchers); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; watchers[key] = {...parentMethod.watchers[key]}; } } if (parentMethod.hooks != null) { - for (const key of Object.keys(parentMethod.hooks)) { - const hook = parentMethod.hooks[key]; + const keys = Object.keys(parentMethod.hooks); + + for (let i = 0; i < keys.length; i++) { + const + key = keys[i], + hook = parentMethod.hooks[key]; hooks[key] = { ...hook, @@ -201,8 +223,12 @@ export function inheritParams(meta: ComponentMeta, parentMeta: ComponentMeta): v export function inheritMods(meta: ComponentMeta, parentMeta: ComponentMeta): void { const {mods} = meta; - for (const modName of Object.keys(parentMeta.mods)) { - const parentModValues = parentMeta.mods[modName]; + const keys = Object.keys(parentMeta.mods); + + for (let i = 0; i < keys.length; i++) { + const + modName = keys[i], + parentModValues = parentMeta.mods[modName]; const currentModValues = mods[modName], diff --git a/src/core/component/meta/interface/meta.ts b/src/core/component/meta/interface/meta.ts index aaf0a583e0..d5d5be3d8d 100644 --- a/src/core/component/meta/interface/meta.ts +++ b/src/core/component/meta/interface/meta.ts @@ -39,14 +39,14 @@ export interface ComponentMeta { name: string; /** - * The name of the NPM package in which the component is defined or overridden + * Component name without any special suffixes */ - layer?: string; + componentName: string; /** - * Component name without any special suffixes + * The name of the NPM package in which the component is defined or overridden */ - componentName: string; + layer?: string; /** * A link to the component's constructor diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index 307e630483..391efa3d79 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -45,11 +45,14 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me const proto = constructor.prototype, - descriptors = Object.getOwnPropertyDescriptors(proto); + descriptors = Object.getOwnPropertyDescriptors(proto), + descriptorKeys = Object.keys(descriptors); let parentProto: CanNull = null; - for (const name of Object.keys(descriptors)) { + for (let i = 0; i < descriptorKeys.length; i++) { + const name = descriptorKeys[i]; + if (name === 'constructor') { continue; } diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index 234ffe21f4..679308488c 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -44,8 +44,12 @@ export function initProps( isFunctional = meta.params.functional === true, source: typeof props = p.forceUpdate ? props : attrs; - for (const propName of Object.keys(source)) { - const prop = source[propName]; + const propNames = Object.keys(source); + + for (let i = 0; i < propNames.length; i++) { + const + propName = propNames[i], + prop = source[propName]; const canSkip = prop == null || diff --git a/src/core/component/queue-emitter/index.ts b/src/core/component/queue-emitter/index.ts index c9bd3c29e9..086a8e1bd8 100644 --- a/src/core/component/queue-emitter/index.ts +++ b/src/core/component/queue-emitter/index.ts @@ -35,11 +35,11 @@ export default class QueueEmitter { */ on(event: Nullable>, handler: Function): void { if (event != null && event.size > 0) { - event.forEach((name) => { + for (const name of event) { const listeners = this.listeners[name] ?? []; listeners.push({event, handler}); this.listeners[name] = listeners; - }); + } return; } @@ -63,10 +63,12 @@ export default class QueueEmitter { const tasks: Array> = []; - queue.forEach((el) => { + for (let i = 0; i < queue.length; i++) { + const el = queue[i]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (el == null) { - return; + continue; } const ev = el.event; @@ -79,7 +81,7 @@ export default class QueueEmitter { tasks.push(task); } } - }); + } if (tasks.length > 0) { return Promise.all(tasks).then(() => undefined); @@ -96,13 +98,13 @@ export default class QueueEmitter { const tasks: Array> = []; - queue.forEach((el) => { - const task = el(); + for (let i = 0; i < queue.length; i++) { + const task = queue[i](); if (Object.isPromise(task)) { tasks.push(task); } - }); + } if (tasks.length > 0) { return Promise.all(tasks).then(() => undefined); diff --git a/src/core/component/reflect/mod.ts b/src/core/component/reflect/mod.ts index 24df579830..04f87cfb6a 100644 --- a/src/core/component/reflect/mod.ts +++ b/src/core/component/reflect/mod.ts @@ -38,10 +38,7 @@ import type { ComponentConstructorInfo } from 'core/component/reflect/interface' * ``` */ export function getComponentMods(component: ComponentConstructorInfo): ModsDecl { - const { - constructor, - componentName - } = component; + const {constructor, componentName} = component; const mods = {}, @@ -49,21 +46,31 @@ export function getComponentMods(component: ComponentConstructorInfo): ModsDecl modsFromConstructor: ModsDecl = {...constructor['mods']}; if (Object.isDictionary(modsFromDS)) { - Object.entries(modsFromDS).forEach(([name, dsModDecl]) => { - const modDecl = modsFromConstructor[name]; - modsFromConstructor[name] = Object.cast(Array.toArray(modDecl, dsModDecl)); - }); + const modNames = Object.keys(modsFromDS); + + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + modDecl = modsFromConstructor[modName]; + + modsFromConstructor[modName] = Object.cast(Array.toArray(modDecl, modsFromDS[modName])); + } } - Object.entries(modsFromConstructor).forEach(([modName, modDecl]) => { - const modValues: Array = []; + const modNames = Object.keys(modsFromConstructor); - if (modDecl != null && modDecl.length > 0) { - const cache = new Map(); + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + modDecl = modsFromConstructor[modName], + modValues: Array = []; + if (modDecl != null && modDecl.length > 0) { let active: CanUndef; - modDecl.forEach((modVal) => { + const cache = new Map(); + + for (const modVal of modDecl) { if (Object.isArray(modVal)) { if (active !== undefined) { cache.set(active, active); @@ -73,23 +80,21 @@ export function getComponentMods(component: ComponentConstructorInfo): ModsDecl cache.set(active, [active]); } else { - const normalizedModVal = Object.isDictionary(modVal) ? - modVal : - String(modVal); + const normalizedModVal = Object.isDictionary(modVal) ? modVal : String(modVal); if (!cache.has(normalizedModVal)) { cache.set(normalizedModVal, normalizedModVal); } } - }); + } - cache.forEach((val) => { + for (const val of cache.values()) { modValues.push(val); - }); + } } mods[modName.camelize(false)] = modValues; - }); + } return mods; } diff --git a/src/core/component/render/helpers/attrs.ts b/src/core/component/render/helpers/attrs.ts index 4e37b19a06..dccd2d8114 100644 --- a/src/core/component/render/helpers/attrs.ts +++ b/src/core/component/render/helpers/attrs.ts @@ -59,8 +59,8 @@ export function resolveAttrs(this: ComponentInterface, vnode: T } if (Object.isArray(children)) { - for (const child of children) { - resolveAttrs.call(this, Object.cast(child)); + for (let i = 0; i < children.length; i++) { + resolveAttrs.call(this, Object.cast(children[i])); } } @@ -141,13 +141,17 @@ export function resolveAttrs(this: ComponentInterface, vnode: T const dynamicProps = vnode.dynamicProps ?? []; vnode.dynamicProps = dynamicProps; - for (const prop of Object.keys(props)) { - if (isHandler.test(prop)) { + const propNames = Object.keys(props); + + for (let i = 0; i < propNames.length; i++) { + const propName = propNames[i]; + + if (isHandler.test(propName)) { if (SSR) { - delete props[prop]; + delete props[propName]; } else { - dynamicProps.push(prop); + dynamicProps.push(propName); } } } @@ -175,7 +179,11 @@ export function resolveAttrs(this: ComponentInterface, vnode: T names = props[key]; if (names != null) { - for (const name of names.split(' ')) { + const nameChunks = names.split(' '); + + for (let i = 0; i < nameChunks.length; i++) { + const name = nameChunks[i]; + if ('classes' in this && this.classes?.[name] != null) { Object.assign(props, mergeProps({class: props.class}, {class: this.classes[name]})); } diff --git a/src/core/component/render/helpers/flags.ts b/src/core/component/render/helpers/flags.ts index 76d89d903e..601ce663d7 100644 --- a/src/core/component/render/helpers/flags.ts +++ b/src/core/component/render/helpers/flags.ts @@ -46,7 +46,9 @@ type PatchFlags = Exclude; * ``` */ export function setVNodePatchFlags(vnode: VNode, ...flags: Flags): void { - for (const flag of flags) { + for (let i = 0; i < flags.length; i++) { + const flag = flags[i]; + const val = flagValues[flag], dest = flagDest[flag]; diff --git a/src/core/component/render/helpers/normalizers.ts b/src/core/component/render/helpers/normalizers.ts index b344208196..9e0b64bd76 100644 --- a/src/core/component/render/helpers/normalizers.ts +++ b/src/core/component/render/helpers/normalizers.ts @@ -20,20 +20,26 @@ export function normalizeClass(classes: CanArray): string { classesStr = classes; } else if (Object.isArray(classes)) { - classes.forEach((className) => { - const normalizedClass = normalizeClass(className); + for (let i = 0; i < classes.length; i += 1) { + const + className = classes[i], + normalizedClass = normalizeClass(className); if (normalizedClass !== '') { classesStr += `${normalizedClass} `; } - }); + } } else if (Object.isDictionary(classes)) { - Object.entries(classes).forEach(([className, has]) => { - if (Object.isTruly(has)) { + const keys = Object.keys(classes); + + for (let i = 0; i < keys.length; i++) { + const className = keys[i]; + + if (Object.isTruly(classes[className])) { classesStr += `${className} `; } - }); + } } return classesStr.trim(); @@ -47,15 +53,22 @@ export function normalizeStyle(styles: CanArray>): s if (Object.isArray(styles)) { const normalizedStyles = {}; - styles.forEach((style) => { + for (let i = 0; i < styles.length; i++) { + const style = styles[i]; + const normalizedStyle = Object.isString(style) ? parseStringStyle(style) : normalizeStyle(style); - if (Object.size(normalizedStyle) > 0) { - Object.entries(normalizedStyle).forEach(([name, style]) => normalizedStyles[name] = style); + if (Object.isDictionary(normalizedStyle)) { + const keys = Object.keys(normalizedStyle); + + for (let i = 0; i < keys.length; i++) { + const name = keys[i]; + normalizedStyles[name] = normalizedStyle[name]; + } } - }); + } return normalizedStyles; } @@ -82,17 +95,19 @@ const export function parseStringStyle(style: string): Dictionary { const styles = {}; - style.split(listDelimiterRgxp).forEach((singleStyle) => { - singleStyle = singleStyle.trim(); + const styleRules = style.split(listDelimiterRgxp); + + for (let i = 0; i < styleRules.length; i++) { + const style = styleRules[i].trim(); - if (singleStyle !== '') { - const chunks = singleStyle.split(propertyDelimiterRgxp, 2); + if (style !== '') { + const chunks = style.split(propertyDelimiterRgxp, 2); if (chunks.length > 1) { styles[chunks[0].trim()] = chunks[1].trim(); } } - }); + } return styles; } @@ -127,12 +142,18 @@ export function normalizeComponentAttrs( normalizedAttrs['v-attrs'] = normalizeComponentAttrs(normalizedAttrs['v-attrs'], dynamicProps, component); } - Object.entries(normalizedAttrs).forEach(normalizeAttr); + const attrNames = Object.keys(normalizedAttrs); + + for (let i = 0; i < attrNames.length; i++) { + const attrName = attrNames[i]; + normalizeAttr(attrName, normalizedAttrs[attrName]); + } + modifyDynamicPath(); return normalizedAttrs; - function normalizeAttr([attrName, value]: [string, unknown]) { + function normalizeAttr(attrName: string, value: unknown) { let propName = `${attrName}Prop`.camelize(false); if (attrName === 'ref' || attrName === 'ref_for') { @@ -162,7 +183,7 @@ export function normalizeComponentAttrs( tiedPropValue = value()[0]; normalizedAttrs[tiedPropName] = tiedPropValue; - normalizeAttr([tiedPropName, tiedPropValue]); + normalizeAttr(tiedPropName, tiedPropValue); dynamicProps.push(tiedPropName); } @@ -200,8 +221,7 @@ export function normalizeComponentAttrs( return; } - // eslint-disable-next-line vars-on-top, no-var - for (var i = dynamicProps.length - 1; i >= 0; i--) { + for (let i = dynamicProps.length - 1; i >= 0; i--) { const prop = dynamicProps[i], path = dynamicPropsPatches.get(prop); diff --git a/src/core/component/render/helpers/props.ts b/src/core/component/render/helpers/props.ts index 197c6af2e4..e807c6be53 100644 --- a/src/core/component/render/helpers/props.ts +++ b/src/core/component/render/helpers/props.ts @@ -28,7 +28,9 @@ export const isHandler = { export function mergeProps(...args: Dictionary[]): Dictionary { const props: Dictionary = {}; - args.forEach((toMerge) => { + for (let i = 0; i < args.length; i += 1) { + const toMerge = args[i]; + for (const key in toMerge) { if (key === 'class') { if (props.class !== toMerge.class) { @@ -47,14 +49,14 @@ export function mergeProps(...args: Dictionary[]): Dictionary { existing !== incoming && !(Object.isArray(existing) && existing.includes(incoming)) ) { - props[key] = Object.isTruly(existing) ? ([]).concat(existing, incoming) : incoming; + props[key] = Object.isTruly(existing) ? Array.toArray(existing, incoming) : incoming; } } else if (key !== '') { props[key] = toMerge[key]; } } - }); + } return props; } diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index e3e7124497..5bbf931fc6 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -184,15 +184,19 @@ export function wrapCreateBlock(original: T): T { const virtualCtx = createVirtualContext(component, {parent: this, props: attrs, slots}); vnode.virtualComponent = virtualCtx; + const filteredAttrs = {}; + const declaredProps = component.props, - filteredAttrs = {}; + propKeys = Object.keys(props); + + for (let i = 0; i < propKeys.length; i++) { + const propName = propKeys[i]; - Object.entries(props).forEach(([key, val]) => { - if (declaredProps[key.camelize(false)] == null) { - filteredAttrs[key] = val; + if (declaredProps[propName.camelize(false)] == null) { + filteredAttrs[propName] = props[propName]; } - }); + } const functionalVNode = virtualCtx.render(virtualCtx, []); @@ -232,12 +236,14 @@ export function wrapCreateBlock(original: T): T { const dynamicProps = vnode.dynamicProps ?? []; vnode.dynamicProps = dynamicProps; - functionalVNode.dynamicProps.forEach((propName) => { + for (let i = 0; i < dynamicProps.length; i++) { + const propName = dynamicProps[i]; + if (isHandler.test(propName)) { dynamicProps.push(propName); setVNodePatchFlags(vnode, 'props'); } - }); + } } functionalVNode.ignore = true; @@ -423,7 +429,9 @@ export function wrapWithDirectives(_: T): T { Object.cast(this.$normalParent) : this; - dirs.forEach((decl) => { + for (let i = 0; i < dirs.length; i++) { + const decl = dirs[i]; + const [dir, value, arg, modifiers] = decl; const binding: DirectiveBinding = { @@ -441,7 +449,8 @@ export function wrapWithDirectives(_: T): T { }; if (!Object.isDictionary(dir)) { - return bindings.push(binding); + bindings.push(binding); + continue; } if (Object.isFunction(dir.beforeCreate)) { @@ -459,7 +468,7 @@ export function wrapWithDirectives(_: T): T { } else if (Object.keys(dir).length > 0) { bindings.push(binding); } - }); + } return vnode; @@ -560,7 +569,9 @@ export function wrapAPI(this: ComponentInterface, path: st async function unrollBuffer(buf: BufItems): Promise { let res = ''; - for (let val of buf) { + for (let i = 0; i < buf.length; i++) { + let val = buf[i]; + if (Object.isPromise(val)) { val = await val; } diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index 2fa0712bb9..ab3c58e637 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -39,6 +39,10 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR const $a = p.async ?? unsafe.$async; + const + allWatchers = p.watchers ?? meta.watchers, + watcherKeys = Object.keys(allWatchers); + const // True if the component is currently deactivated isDeactivated = hook === 'deactivated', @@ -49,12 +53,14 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR // True if the method has been invoked with passing the custom async instance as a property isCustomAsync = $a !== unsafe.$async; - const watchers = p.watchers ?? meta.watchers; - // Iterate over all registered watchers and listeners and initialize their - Object.entries(watchers).forEach(([watchPath, watchers]) => { + for (let i = 0; i < watcherKeys.length; i++) { + let watchPath = watcherKeys[i]; + + const watchers = allWatchers[watchPath]; + if (watchers == null) { - return; + continue; } // A link to the context of the watcher; @@ -77,21 +83,7 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR attachWatcherOnMounted = hookMod === '?'; } - // Add a listener to the component's created hook if the component has not been created yet - if (attachWatcherOnCreated && isBeforeCreate) { - hooks['before:created'].push({fn: attachWatcher}); - return; - } - - // Add a listener to the component's mounted/activated hook if the component has not been mounted or activated yet - if (attachWatcherOnMounted && (isBeforeCreate || component.$el == null)) { - hooks[isDeactivated ? 'activated' : 'mounted'].unshift({fn: attachWatcher}); - return; - } - - attachWatcher(); - - function attachWatcher() { + const attachWatcher = () => { // If we have a custom watcher, we need to find a link to the event emitter. // For instance: // ':foo' -> watcherCtx == ctx; key = 'foo' @@ -112,16 +104,18 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR let propInfo: typeof p.info = p.info; // Iterates over all registered handlers for this watcher - watchers!.forEach((watchInfo) => { + for (let i = 0; i < watchers.length; i++) { + const watchInfo = watchers[i]; + if (watchInfo.shouldInit?.(component) === false) { - return; + continue; } if (customWatcher == null) { propInfo ??= getPropertyInfo(watchPath, component); if (canSkipWatching(propInfo, watchInfo)) { - return; + continue; } } @@ -362,7 +356,19 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR link = $a.on(emitter, 'mutation', handler, wrapWithSuspending(asyncParams, 'watchers')); unwatch = $watch.call(component, propInfo, watchInfo, handler); } - }); + } + }; + + // Add a listener to the component's created hook if the component has not been created yet + if (attachWatcherOnCreated && isBeforeCreate) { + hooks['before:created'].push({fn: attachWatcher}); + + // Add a listener to the component's mounted/activated hook if the component has not been mounted or activated yet + } else if (attachWatcherOnMounted && (isBeforeCreate || component.$el == null)) { + hooks[isDeactivated ? 'activated' : 'mounted'].unshift({fn: attachWatcher}); + + } else { + attachWatcher(); } - }); + } } diff --git a/src/core/component/watch/component-api.ts b/src/core/component/watch/component-api.ts index c6bfae70c3..e76528e591 100644 --- a/src/core/component/watch/component-api.ts +++ b/src/core/component/watch/component-api.ts @@ -85,14 +85,13 @@ export function implementComponentWatchAPI(component: ComponentInterface): void withProto: true }; - watchDependencies.forEach((deps, path) => { - const - newDeps: typeof deps = []; + for (const [path, deps] of watchDependencies) { + const newDeps: typeof deps = []; - let - needForkDeps = false; + let needForkDeps = false; - deps.forEach((dep, i) => { + for (let i = 0; i < deps.length; i++) { + const dep = deps[i]; newDeps[i] = dep; const @@ -102,7 +101,7 @@ export function implementComponentWatchAPI(component: ComponentInterface): void if (watchInfo.ctx === component && !watchDependencies.has(dep)) { needForkDeps = true; newDeps[i] = watchInfo.path; - return; + continue; } const invalidateCache = (value, oldValue, info) => { @@ -132,10 +131,11 @@ export function implementComponentWatchAPI(component: ComponentInterface): void mutations = [Object.cast([mutations, ...args])]; } - const - modifiedMutations: Array<[unknown, unknown, WatchHandlerParams]> = []; + const modifiedMutations: Array<[unknown, unknown, WatchHandlerParams]> = []; + + for (let i = 0; i < mutations.length; i++) { + const [value, oldValue, info] = mutations[i]; - mutations.forEach(([value, oldValue, info]) => { modifiedMutations.push([ value, oldValue, @@ -150,19 +150,18 @@ export function implementComponentWatchAPI(component: ComponentInterface): void parent: {value, oldValue, info} }) ]); - }); + } broadcastAccessorMutations(modifiedMutations); }; attachDynamicWatcher(component, watchInfo, watchOpts, broadcastMutations, dynamicHandlers); - }); + } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (needForkDeps) { watchDependencies.set(path, newDeps); } - }); + } } // Watcher of fields @@ -274,10 +273,10 @@ export function implementComponentWatchAPI(component: ComponentInterface): void invalidateComputedCache[tiedWatchers] = tiedLinks; broadcastAccessorMutations[tiedWatchers] = tiedLinks; - props.forEach((prop) => { + for (const prop of props) { unsafe.$watch(prop, {...propWatchOpts, flush: 'sync'}, invalidateComputedCache); unsafe.$watch(prop, propWatchOpts, broadcastAccessorMutations); - }); + } } } } @@ -360,9 +359,11 @@ export function implementComponentWatchAPI(component: ComponentInterface): void ctx = invalidateComputedCache[tiedWatchers] != null ? component : info.root[toComponentObject] ?? component, currentDynamicHandlers = immediateDynamicHandlers.get(ctx)?.[rootKey]; - currentDynamicHandlers?.forEach((handler) => { - handler(val, oldVal, info); - }); + if (currentDynamicHandlers != null) { + for (const handler of currentDynamicHandlers) { + handler(val, oldVal, info); + } + } }; } @@ -374,20 +375,20 @@ export function implementComponentWatchAPI(component: ComponentInterface): void mutations = [Object.cast([mutations, ...args])]; } - mutations.forEach(([val, oldVal, info]) => { - const - {path} = info; + for (let i = 0; i < mutations.length; i++) { + const [val, oldVal, info] = mutations[i]; + + const {path} = info; if (path[path.length - 1] === '__proto__') { - return; + continue; } if (info.parent != null) { - const - {path: parentPath} = info.parent.info; + const {path: parentPath} = info.parent.info; if (parentPath[parentPath.length - 1] === '__proto__') { - return; + continue; } } @@ -396,24 +397,26 @@ export function implementComponentWatchAPI(component: ComponentInterface): void ctx = emitAccessorEvents[tiedWatchers] != null ? component : info.root[toComponentObject] ?? component, currentDynamicHandlers = dynamicHandlers.get(ctx)?.[rootKey]; - currentDynamicHandlers?.forEach((handler) => { - // Because we register several watchers (props, fields, etc.) at the same time, - // we need to control that every dynamic handler must be invoked no more than one time per tick - if (usedHandlers.has(handler)) { - return; + if (currentDynamicHandlers != null) { + for (const handler of currentDynamicHandlers) { + // Because we register several watchers (props, fields, etc.) at the same time, + // we need to control that every dynamic handler must be invoked no more than one time per tick + if (usedHandlers.has(handler)) { + continue; + } + + handler(val, oldVal, info); + usedHandlers.add(handler); + + if (timerId == null) { + timerId = setImmediate(() => { + timerId = undefined; + usedHandlers.clear(); + }); + } } - - handler(val, oldVal, info); - usedHandlers.add(handler); - - if (timerId == null) { - timerId = setImmediate(() => { - timerId = undefined; - usedHandlers.clear(); - }); - } - }); - }); + } + } }; } } diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 4d62160a00..258402b8c0 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -401,9 +401,11 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface const tiedLinks = handler[tiedWatchers]; if (Object.isArray(tiedLinks)) { - tiedLinks.forEach((path) => { + for (let i = 0; i < tiedLinks.length; i++) { + const path = tiedLinks[i]; + if (!Object.isArray(path)) { - return; + continue; } const modifiedInfo: WatchHandlerParams = { @@ -413,7 +415,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface }; handler.call(this, value, oldValue, modifiedInfo); - }); + } } else { handler.call(this, value, oldValue, info); @@ -434,7 +436,9 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface // This situation occurs when the root observable object has changed, // and we need to remove the watchers of all its "nested parts", but leave the root watcher intact - destructors.splice(1, destructors.length).forEach((destroy) => destroy()); + for (const destroy of destructors.splice(1, destructors.length)) { + destroy(); + } if (fromSystem) { i.path = [isPrivateField.replace(String(i.path[0])), ...i.path.slice(1)]; @@ -476,9 +480,11 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface const tiedLinks = handler[tiedWatchers]; if (Object.isArray(tiedLinks)) { - tiedLinks.forEach((path) => { + for (let i = 0; i < tiedLinks.length; i++) { + const path = tiedLinks[i]; + if (!Object.isArray(path)) { - return; + continue; } const modifiedInfo: WatchHandlerParams = { @@ -488,7 +494,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface }; externalWatchHandler(value, oldValue, modifiedInfo); - }); + } } else { externalWatchHandler(value, oldValue, info); @@ -520,8 +526,9 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface attachDeepProxy(forceUpdate); return wrapDestructor(() => { - destructors.forEach((destroy) => destroy()); - destructors.splice(0, destructors.length); + for (const destroy of destructors.splice(0, destructors.length)) { + destroy(); + } }); } diff --git a/src/core/component/watch/helpers.ts b/src/core/component/watch/helpers.ts index e4cd72b079..a79504e05c 100644 --- a/src/core/component/watch/helpers.ts +++ b/src/core/component/watch/helpers.ts @@ -109,9 +109,8 @@ export function attachDynamicWatcher( const filteredMutations: unknown[] = []; - mutations.forEach((mutation) => { - const - [value, oldValue, info] = mutation; + for (const mutation of mutations) { + const [value, oldValue, info] = mutation; if ( // We don't watch deep mutations @@ -123,11 +122,11 @@ export function attachDynamicWatcher( // The mutation has been already fired watchOpts.eventFilter && !Object.isTruly(watchOpts.eventFilter(value, oldValue, info)) ) { - return; + continue; } filteredMutations.push(mutation); - }); + } if (filteredMutations.length > 0) { if (isPacked) { From 739f8e8c066c5f5e553e2b27fa2851113b54e54b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 10 Oct 2024 17:12:54 +0300 Subject: [PATCH 219/334] fix: fixes after refactoring --- src/core/component/meta/method.ts | 46 +++++++++---------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index 391efa3d79..21bcdc63f8 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -48,8 +48,6 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me descriptors = Object.getOwnPropertyDescriptors(proto), descriptorKeys = Object.keys(descriptors); - let parentProto: CanNull = null; - for (let i = 0; i < descriptorKeys.length; i++) { const name = descriptorKeys[i]; @@ -163,37 +161,19 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me const old = store[name], - set = desc.set ?? old?.set, - get = desc.get ?? old?.get; - - parentProto ??= Object.getPrototypeOf(proto); - - if (name in parentProto!) { - // To use `super` within the setter, we also create a new method with a name `${key}Setter` - if (set != null) { - const methodName = `${name}Setter`; - proto[methodName] = set; - - meta.methods[methodName] = { - src, - fn: set, - watchers: {}, - hooks: {} - }; - } - - // To using `super` within the getter, we also create a new method with a name `${key}Getter` - if (get != null) { - const methodName = `${name}Getter`; - proto[methodName] = get; - - meta.methods[methodName] = { - src, - fn: get, - watchers: {}, - hooks: {} - }; - } + set = desc.set ?? old?.set; + + // To use `super` within the setter, we also create a new method with a name `${key}Setter` + if (set != null) { + const methodName = `${name}Setter`; + proto[methodName] = set; + + meta.methods[methodName] = { + src, + fn: set, + watchers: {}, + hooks: {} + }; } const accessor: ComponentAccessor = Object.assign(store[name] ?? {cache: false}, { From fccb86233ffa505aa69108793d0a613796335772 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 14 Oct 2024 16:18:35 +0300 Subject: [PATCH 220/334] fix: fixes after refactoring --- src/components/super/i-block/i-block.ts | 5 +---- src/core/component/meta/fork.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/super/i-block/i-block.ts b/src/components/super/i-block/i-block.ts index b647be0dce..2010a254bb 100644 --- a/src/components/super/i-block/i-block.ts +++ b/src/components/super/i-block/i-block.ts @@ -100,10 +100,7 @@ export default abstract class iBlock extends iBlockProviders { @hook('before:mounted') protected onMountTeleports(): void { - const { - $el: originalNode, - $async: $a - } = this; + const {$el: originalNode, $async: $a} = this; if (originalNode == null) { return; diff --git a/src/core/component/meta/fork.ts b/src/core/component/meta/fork.ts index 3bcf6f0fc2..c184cb12b0 100644 --- a/src/core/component/meta/fork.ts +++ b/src/core/component/meta/fork.ts @@ -34,7 +34,7 @@ export function forkMeta(base: ComponentMeta): ComponentMeta { for (let i = 0; i < watcherNames.length; i++) { const - name = hookNames[i], + name = watcherNames[i], watchers = base.watchers[name]; if (watchers != null) { From b916fb6dc881d4c6732a561ce58b1c5301ebc6b6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 14 Oct 2024 16:28:09 +0300 Subject: [PATCH 221/334] fix: fixes after refactoring --- src/core/component/meta/method.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts index 21bcdc63f8..b6743b8da9 100644 --- a/src/core/component/meta/method.ts +++ b/src/core/component/meta/method.ts @@ -161,7 +161,8 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me const old = store[name], - set = desc.set ?? old?.set; + set = desc.set ?? old?.set, + get = desc.get ?? old?.get; // To use `super` within the setter, we also create a new method with a name `${key}Setter` if (set != null) { @@ -176,9 +177,22 @@ export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = me }; } + // To using `super` within the getter, we also create a new method with a name `${key}Getter` + if (get != null) { + const methodName = `${name}Getter`; + proto[methodName] = get; + + meta.methods[methodName] = { + src, + fn: get, + watchers: {}, + hooks: {} + }; + } + const accessor: ComponentAccessor = Object.assign(store[name] ?? {cache: false}, { src, - get: desc.get ?? old?.get, + get, set }); From 391cf4096979e5a718187a4b0f68e2df067c4a77 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 15 Oct 2024 13:41:21 +0300 Subject: [PATCH 222/334] fix: fixes propName normalization --- build/snakeskin/default-filters.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/build/snakeskin/default-filters.js b/build/snakeskin/default-filters.js index ad5796db70..ec345cccfc 100644 --- a/build/snakeskin/default-filters.js +++ b/build/snakeskin/default-filters.js @@ -63,11 +63,14 @@ function tagFilter({name: tag, attrs = {}}, _, rootTag, forceRenderAsVNode, tplN if (isStaticV4Prop.test(key)) { // Since HTML is not case-sensitive, the name can be written differently. // We will explicitly normalize the name to the most popular format for HTML notation. - const tmp = key.dasherize(key.startsWith(':')); + // For Vue attributes such as `:` and `@`, we convert the prop to camelCase format. + const normalizedKey = key.startsWith(':') || key.startsWith('@') ? + key.camelize(false) : + key.dasherize(); - if (tmp !== key) { + if (normalizedKey !== key) { delete attrs[key]; - attrs[tmp] = attr; + attrs[normalizedKey] = attr; } } }); From b0ca3366b8e22a4b89055f99cde16de7d16a7dc4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 15 Oct 2024 13:43:13 +0300 Subject: [PATCH 223/334] fix: fixes after refactoring --- src/core/component/decorators/hook/decorator.ts | 2 +- src/core/component/watch/bind.ts | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/core/component/decorators/hook/decorator.ts b/src/core/component/decorators/hook/decorator.ts index 48053ebd0e..3a47008525 100644 --- a/src/core/component/decorators/hook/decorator.ts +++ b/src/core/component/decorators/hook/decorator.ts @@ -68,6 +68,6 @@ export function hook(hook: DecoratorHook): PartDecorator { } } - meta.methods[methodName] = normalizeFunctionalParams({...method}, meta); + meta.methods[methodName] = normalizeFunctionalParams({...method, hooks}, meta); }); } diff --git a/src/core/component/watch/bind.ts b/src/core/component/watch/bind.ts index ab3c58e637..f28c8fb42f 100644 --- a/src/core/component/watch/bind.ts +++ b/src/core/component/watch/bind.ts @@ -282,12 +282,6 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - const propInfo = p.info ?? getPropertyInfo(watchPath, component); - - if (canSkipWatching(propInfo, watchInfo)) { - return; - } - /* eslint-disable prefer-const */ let @@ -333,12 +327,6 @@ export function bindRemoteWatchers(component: ComponentInterface, params?: BindR return; } - const propInfo = p.info ?? getPropertyInfo(watchPath, component); - - if (canSkipWatching(propInfo, watchInfo)) { - return; - } - /* eslint-disable prefer-const */ let From 4e05cff733db46fa914a178be9aa284c7c2a3b37 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 15 Oct 2024 13:57:05 +0300 Subject: [PATCH 224/334] fix: fixes prop normalization --- build/snakeskin/default-filters.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/snakeskin/default-filters.js b/build/snakeskin/default-filters.js index ec345cccfc..9683cd24d9 100644 --- a/build/snakeskin/default-filters.js +++ b/build/snakeskin/default-filters.js @@ -59,12 +59,14 @@ Snakeskin.importFilters({ }); function tagFilter({name: tag, attrs = {}}, _, rootTag, forceRenderAsVNode, tplName, cursor) { + const needCamelize = /^(:[^-]|@)/; + Object.entries(attrs).forEach(([key, attr]) => { if (isStaticV4Prop.test(key)) { // Since HTML is not case-sensitive, the name can be written differently. // We will explicitly normalize the name to the most popular format for HTML notation. // For Vue attributes such as `:` and `@`, we convert the prop to camelCase format. - const normalizedKey = key.startsWith(':') || key.startsWith('@') ? + const normalizedKey = needCamelize.test(key) ? key.camelize(false) : key.dasherize(); From b772ee995306318c19c78d0e22adecb500f747a9 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 15 Oct 2024 14:05:07 +0300 Subject: [PATCH 225/334] fix: fixes after refactoring --- src/core/component/render/wrappers.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index 5bbf931fc6..dab13d410d 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -233,11 +233,13 @@ export function wrapCreateBlock(original: T): T { } if (!SSR && functionalVNode.dynamicProps != null && functionalVNode.dynamicProps.length > 0) { + const functionalProps = functionalVNode.dynamicProps; + const dynamicProps = vnode.dynamicProps ?? []; vnode.dynamicProps = dynamicProps; - for (let i = 0; i < dynamicProps.length; i++) { - const propName = dynamicProps[i]; + for (let i = 0; i < functionalProps.length; i++) { + const propName = functionalProps[i]; if (isHandler.test(propName)) { dynamicProps.push(propName); From 5c08c79fbf628d54928aa2bac616491b2e7c34b2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 15 Oct 2024 14:44:37 +0300 Subject: [PATCH 226/334] fix: fixes after refactoring --- src/core/component/field/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/field/init.ts b/src/core/component/field/init.ts index 1663480b41..4e70850e93 100644 --- a/src/core/component/field/init.ts +++ b/src/core/component/field/init.ts @@ -32,7 +32,7 @@ export function initFields( const sourceVal = store[name]; - if (field?.init == null) { + if (sourceVal !== undefined || field?.init == null) { store[name] = sourceVal; continue; } From a417b3100d7a1e671e18eee201655fc16fb73714 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 15 Oct 2024 15:12:12 +0300 Subject: [PATCH 227/334] chore: stylish fixes --- src/core/component/decorators/prop/decorator.ts | 5 +---- src/core/component/decorators/system/decorator.ts | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index e94a1d7367..9e5fe15104 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -136,10 +136,7 @@ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { // it is necessary to clone this value for each new component instance // to ensure that they do not share the same value if (prop.type !== Function && defaultInstanceValue != null && typeof defaultInstanceValue === 'object') { - getDefault = () => Object.isPrimitive(defaultInstanceValue) ? - defaultInstanceValue : - Object.fastClone(defaultInstanceValue); - + getDefault = () => Object.fastClone(defaultInstanceValue); (getDefault)[DEFAULT_WRAPPER] = true; } diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index 8a96143d60..ec7e644891 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -145,15 +145,15 @@ export function system( getDefValue = () => field.default; } else if (meta.instance[fieldName] !== undefined) { - const val = meta.instance[fieldName]; + const defaultInstanceValue = meta.instance[fieldName]; - if (Object.isPrimitive(val)) { - getDefValue = () => val; + if (Object.isPrimitive(defaultInstanceValue)) { + getDefValue = () => defaultInstanceValue; } else { // To prevent linking to the same type of component for non-primitive values, // it's important to clone the default value from the component constructor. - getDefValue = () => Object.fastClone(val); + getDefValue = () => Object.fastClone(defaultInstanceValue); } } From ab0dd8f0f9765de689f608d46b895c5a4fea862f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 13:41:59 +0300 Subject: [PATCH 228/334] feat: added the new @defaultValue decorator --- src/core/component/const/symbols.ts | 5 - .../decorators/default-value/decorator.ts | 50 ++++ src/core/component/decorators/helpers.ts | 5 +- src/core/component/decorators/interface.ts | 2 +- .../component/decorators/prop/decorator.ts | 241 ++++++++--------- .../component/decorators/system/decorator.ts | 249 +++++++++--------- src/core/component/prop/init.ts | 4 +- 7 files changed, 299 insertions(+), 257 deletions(-) create mode 100644 src/core/component/decorators/default-value/decorator.ts diff --git a/src/core/component/const/symbols.ts b/src/core/component/const/symbols.ts index 8cc5c0efb5..dc36e66a9a 100644 --- a/src/core/component/const/symbols.ts +++ b/src/core/component/const/symbols.ts @@ -11,11 +11,6 @@ */ export const V4_COMPONENT = Symbol('This is a V4Fire component'); -/** - * A symbol used as a flag to mark a function as a generated default wrapper - */ -export const DEFAULT_WRAPPER = Symbol('This function is the generated default wrapper'); - /** * A placeholder object used to refer to the parent instance in a specific context */ diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts new file mode 100644 index 0000000000..3bbc7186cb --- /dev/null +++ b/src/core/component/decorators/default-value/decorator.ts @@ -0,0 +1,50 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { regProp } from 'core/component/decorators/prop'; +import { regField } from 'core/component/decorators/system'; + +import { createComponentDecorator } from 'core/component/decorators/helpers'; + +import type { PartDecorator } from 'core/component/decorators/interface'; + +/** + * Sets a default value for the specified prop or component field. + * The value is set using a getter function. + * + * Typically, this decorator does not need to be used explicitly, + * as it will be automatically added in the appropriate places during the build process. + * + * @decorator + * @param [getter] - a function that returns the default value for a prop or field. + * + * @example + * ```typescript + * import iBlock, { component, prop, defaultValue } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @defaultValue(() => 0) + * @prop(Number) + * bla!: number; + * } + * ``` + */ +export function defaultValue(getter: () => unknown): PartDecorator { + return createComponentDecorator(({meta}, key) => { + if (key in meta.props) { + regProp(key, {default: getter}, meta); + + } else if (key in meta.fields) { + regField(key, 'fields', {init: getter}, meta); + + } else { + regField(key, 'systemFields', {init: getter}, meta); + } + }); +} diff --git a/src/core/component/decorators/helpers.ts b/src/core/component/decorators/helpers.ts index 144e6d635c..bf7a8a27ae 100644 --- a/src/core/component/decorators/helpers.ts +++ b/src/core/component/decorators/helpers.ts @@ -17,6 +17,7 @@ import type { PartDecorator, ComponentPartDecorator, + ComponentDescriptor, DecoratorFunctionalOptions } from 'core/component/decorators/interface'; @@ -27,13 +28,13 @@ import type { */ export function createComponentDecorator(decorator: ComponentPartDecorator): PartDecorator { return (_: object, partKey: string, partDesc?: PropertyDescriptor) => { - initEmitter.once('bindConstructor', (componentName, regEvent) => { + initEmitter.once('bindConstructor', (componentName: string, regEvent: string) => { const decoratedKeys = componentDecoratedKeys[componentName] ?? new Set(); componentDecoratedKeys[componentName] = decoratedKeys; decoratedKeys.add(partKey); - initEmitter.once(regEvent, (componentDesc) => { + initEmitter.once(regEvent, (componentDesc: ComponentDescriptor) => { decorator(componentDesc, partKey, partDesc); }); }); diff --git a/src/core/component/decorators/interface.ts b/src/core/component/decorators/interface.ts index 1891b9bdfb..82f955c7dd 100644 --- a/src/core/component/decorators/interface.ts +++ b/src/core/component/decorators/interface.ts @@ -16,7 +16,7 @@ export interface DecoratorFunctionalOptions { functional?: boolean; } -interface ComponentDescriptor { +export interface ComponentDescriptor { meta: ComponentMeta; parentMeta: CanNull; } diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 9e5fe15104..2bae4b4abe 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -7,15 +7,14 @@ */ import { defProp } from 'core/const/props'; -import { DEFAULT_WRAPPER } from 'core/component/const'; import { isBinding } from 'core/component/reflect'; import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; +import type { ComponentMeta } from 'core/component/meta'; import type { ComponentProp, ComponentField } from 'core/component/interface'; import type { PartDecorator } from 'core/component/decorators/interface'; - import type { DecoratorProp, PropType } from 'core/component/decorators/prop/interface'; /** @@ -42,173 +41,167 @@ import type { DecoratorProp, PropType } from 'core/component/decorators/prop/int * ``` */ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { - return createComponentDecorator(({meta}, propName) => { - const params: DecoratorProp = Object.isFunction(typeOrParams) || Object.isArray(typeOrParams) ? - {type: typeOrParams, forceUpdate: true} : - {forceUpdate: true, ...typeOrParams}; + return createComponentDecorator((desc, propName) => { + regProp(propName, typeOrParams, desc.meta); + }); +} - delete meta.methods[propName]; +/** + * Registers a component prop to the specified metaobject + * + * @param propName - the name of the property + * @param typeOrParams - a constructor of the property type or an object with property parameters + * @param meta - the metaobject where the property is registered + */ +export function regProp(propName: string, typeOrParams: Nullable, meta: ComponentMeta): void { + const params: DecoratorProp = Object.isFunction(typeOrParams) || Object.isArray(typeOrParams) ? + {type: typeOrParams, forceUpdate: true} : + {forceUpdate: true, ...typeOrParams}; - const accessors = meta.accessors[propName] != null ? - meta.accessors : - meta.computedFields; + delete meta.methods[propName]; - if (accessors[propName] != null) { - Object.defineProperty(meta.constructor.prototype, propName, defProp); - delete accessors[propName]; - } + const accessors = propName in meta.accessors ? + meta.accessors : + meta.computedFields; - // Handling the situation when a field changes type during inheritance, - // for example, it was a @system in the parent component and became a @prop - for (const anotherType of ['fields', 'systemFields']) { - const cluster = meta[anotherType]; + if (accessors[propName] != null) { + Object.defineProperty(meta.constructor.prototype, propName, defProp); + delete accessors[propName]; + } - if (propName in cluster) { - const field: ComponentField = {...cluster[propName]}; + // Handling the situation when a field changes type during inheritance, + // for example, it was a @system in the parent component and became a @prop + for (const anotherType of ['fields', 'systemFields']) { + const cluster = meta[anotherType]; - // Do not inherit the `functional` option in this case - delete field.functional; + if (propName in cluster) { + const field: ComponentField = {...cluster[propName]}; - // The option `init` cannot be converted to `default` - delete field.init; + // Do not inherit the `functional` option in this case + delete field.functional; - meta.props[propName] = {...field, forceUpdate: true}; + // The option `init` cannot be converted to `default` + delete field.init; - delete cluster[propName]; + meta.props[propName] = {...field, forceUpdate: true}; - break; - } + delete cluster[propName]; + + break; } + } - let prop: ComponentProp = meta.props[propName] ?? { - forceUpdate: true, - meta: {} - }; + let prop: ComponentProp = meta.props[propName] ?? { + forceUpdate: true, + meta: {} + }; - let {watchers} = prop; + let {watchers} = prop; - if (params.watch != null) { - watchers ??= new Map(); + if (params.watch != null) { + watchers ??= new Map(); - for (const fieldWatcher of Array.toArray(params.watch)) { - if (Object.isPlainObject(fieldWatcher)) { - // FIXME: remove Object.cast - watchers.set(fieldWatcher.handler, Object.cast(normalizeFunctionalParams({...fieldWatcher}, meta))); + for (const fieldWatcher of Array.toArray(params.watch)) { + if (Object.isPlainObject(fieldWatcher)) { + // FIXME: remove Object.cast + watchers.set(fieldWatcher.handler, Object.cast(normalizeFunctionalParams({...fieldWatcher}, meta))); - } else { - // FIXME: remove Object.cast - watchers.set(fieldWatcher, Object.cast(normalizeFunctionalParams({handler: fieldWatcher}, meta))); - } + } else { + // FIXME: remove Object.cast + watchers.set(fieldWatcher, Object.cast(normalizeFunctionalParams({handler: fieldWatcher}, meta))); } } + } - prop = normalizeFunctionalParams({ - ...prop, - ...params, - - watchers, - - meta: { - ...prop.meta, - ...params.meta - } - }, meta); - - meta.props[propName] = prop; + prop = normalizeFunctionalParams({ + ...prop, + ...params, - let defaultValue: unknown; + watchers, - const - isRoot = meta.params.root === true, - isFunctional = meta.params.functional === true, - defaultProps = meta.params.defaultProps !== false; - - if (defaultProps || prop.forceDefault) { - if (prop.default !== undefined) { - defaultValue = prop.default; - - } else { - const defaultInstanceValue = meta.instance[propName]; + meta: { + ...prop.meta, + ...params.meta + } + }, meta); - let getDefault = defaultInstanceValue; + meta.props[propName] = prop; - // If the default value of a prop is set via a default value for a class property, - // it is necessary to clone this value for each new component instance - // to ensure that they do not share the same value - if (prop.type !== Function && defaultInstanceValue != null && typeof defaultInstanceValue === 'object') { - getDefault = () => Object.fastClone(defaultInstanceValue); - (getDefault)[DEFAULT_WRAPPER] = true; - } + let defaultValue: unknown; - defaultValue = getDefault; - } - } + const + isRoot = meta.params.root === true, + isFunctional = meta.params.functional === true, + defaultProps = meta.params.defaultProps !== false; - if (!isRoot || defaultValue !== undefined) { - const {component} = meta; + if (prop.default !== undefined && (defaultProps || prop.forceDefault)) { + defaultValue = prop.default; + } - (prop.forceUpdate ? component.props : component.attrs)[propName] = { - type: prop.type, - required: prop.required !== false && defaultProps && defaultValue === undefined, + if (!isRoot || defaultValue !== undefined) { + const {component} = meta; - default: defaultValue, - functional: prop.functional, + (prop.forceUpdate ? component.props : component.attrs)[propName] = { + type: prop.type, + required: prop.required !== false && defaultProps && defaultValue === undefined, - // eslint-disable-next-line @v4fire/unbound-method - validator: prop.validator - }; - } + default: defaultValue, + functional: prop.functional, - const canWatchProps = !SSR && !isRoot && !isFunctional; + // eslint-disable-next-line @v4fire/unbound-method + validator: prop.validator + }; + } - if (canWatchProps || watchers != null && watchers.size > 0) { - meta.metaInitializers.set(propName, (meta) => { - const {watchPropDependencies} = meta; + const canWatchProps = !SSR && !isRoot && !isFunctional; - const - isFunctional = meta.params.functional === true, - canWatchProps = !SSR && !isRoot && !isFunctional; + if (canWatchProps || watchers != null && watchers.size > 0) { + meta.metaInitializers.set(propName, (meta) => { + const {watchPropDependencies} = meta; - const watcherListeners = meta.watchers[propName] ?? []; - meta.watchers[propName] = watcherListeners; + const + isFunctional = meta.params.functional === true, + canWatchProps = !SSR && !isRoot && !isFunctional; - if (watchers != null) { - for (const watcher of watchers.values()) { - if (isFunctional && watcher.functional === false || !canWatchProps && !watcher.immediate) { - continue; - } + const watcherListeners = meta.watchers[propName] ?? []; + meta.watchers[propName] = watcherListeners; - watcherListeners.push(watcher); + if (watchers != null) { + for (const watcher of watchers.values()) { + if (isFunctional && watcher.functional === false || !canWatchProps && !watcher.immediate) { + continue; } + + watcherListeners.push(watcher); } + } - if (canWatchProps) { - const normalizedName = isBinding.test(propName) ? isBinding.replace(propName) : propName; + if (canWatchProps) { + const normalizedName = isBinding.test(propName) ? isBinding.replace(propName) : propName; - if ((meta.computedFields[normalizedName] ?? meta.accessors[normalizedName]) != null) { - const props = watchPropDependencies.get(normalizedName) ?? new Set(); + if ((meta.computedFields[normalizedName] ?? meta.accessors[normalizedName]) != null) { + const props = watchPropDependencies.get(normalizedName) ?? new Set(); - props.add(propName); - watchPropDependencies.set(normalizedName, props); + props.add(propName); + watchPropDependencies.set(normalizedName, props); - } else { - for (const [path, deps] of meta.watchDependencies) { - for (const dep of deps) { - const pathChunks = Object.isArray(dep) ? dep : dep.split('.', 1); + } else { + for (const [path, deps] of meta.watchDependencies) { + for (const dep of deps) { + const pathChunks = Object.isArray(dep) ? dep : dep.split('.', 1); - if (pathChunks[0] === propName) { - const props = watchPropDependencies.get(path) ?? new Set(); + if (pathChunks[0] === propName) { + const props = watchPropDependencies.get(path) ?? new Set(); - props.add(propName); - watchPropDependencies.set(path, props); + props.add(propName); + watchPropDependencies.set(path, props); - break; - } + break; } } } } - }); - } - }); + } + }); + } } diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index ec7e644891..b02a5d520e 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -12,9 +12,9 @@ import { isStore } from 'core/component/reflect'; import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; import type { ComponentField } from 'core/component/interface'; +import type { ComponentMeta } from 'core/component/meta'; import type { PartDecorator } from 'core/component/decorators/interface'; - import type { InitFieldFn, DecoratorSystem, DecoratorField } from 'core/component/decorators/system/interface'; const INIT = Symbol('The field initializer'); @@ -56,160 +56,165 @@ export function system( initOrParams?: InitFieldFn | DecoratorSystem | DecoratorField, type: 'fields' | 'systemFields' = 'systemFields' ): PartDecorator { - return createComponentDecorator(({meta}, fieldName) => { - const params = Object.isFunction(initOrParams) ? {init: initOrParams} : {...initOrParams}; + return createComponentDecorator((desc, fieldName) => { + regField(fieldName, type, initOrParams, desc.meta); + }); +} - delete meta.methods[fieldName]; +/** + * Registers a component field to the specified metaobject + * + * @param fieldName - the name of the field + * @param type - the type of the registered field: `systemFields` or `fields` + * @param initOrParams - a function to initialize the field value or an object with field parameters + * @param meta - the metaobject where the field is registered + */ +export function regField( + fieldName: string, + type: 'systemFields' | 'fields', + initOrParams: Nullable, + meta: ComponentMeta +): void { + const params = Object.isFunction(initOrParams) ? {init: initOrParams} : {...initOrParams}; + + delete meta.methods[fieldName]; + + const accessors = fieldName in meta.accessors ? + meta.accessors : + meta.computedFields; + + if (accessors[fieldName] != null) { + Object.defineProperty(meta.constructor.prototype, fieldName, defProp); + delete accessors[fieldName]; + } + + // Handling the situation when a field changes type during inheritance, + // for example, it was a @prop in the parent component and became a @system + for (const anotherType of ['props', type === 'fields' ? 'systemFields' : 'fields']) { + const cluster = meta[anotherType]; + + if (fieldName in cluster) { + const field: ComponentField = {...cluster[fieldName]}; + + // Do not inherit the `functional` option in this case + delete field.functional; + + if (anotherType === 'props') { + delete meta.component.props[fieldName]; + + if (Object.isFunction(field.default)) { + field.init = field.default; + delete field.default; + } + } - const accessors = meta.accessors[fieldName] != null ? - meta.accessors : - meta.computedFields; + meta[type][fieldName] = field; + delete cluster[fieldName]; - if (accessors[fieldName] != null) { - Object.defineProperty(meta.constructor.prototype, fieldName, defProp); - delete accessors[fieldName]; + break; } + } - // Handling the situation when a field changes type during inheritance, - // for example, it was a @prop in the parent component and became a @system - for (const anotherType of ['props', type === 'fields' ? 'systemFields' : 'fields']) { - const cluster = meta[anotherType]; - - if (fieldName in cluster) { - const field: ComponentField = {...cluster[fieldName]}; + let field: ComponentField = meta[type][fieldName] ?? { + src: meta.componentName, + meta: {} + }; - // Do not inherit the `functional` option in this case - delete field.functional; + let {watchers, after} = field; - if (anotherType === 'props') { - delete meta.component.props[fieldName]; + if (params.after != null) { + after = new Set(Array.toArray(params.after)); + } - if (Object.isFunction(field.default)) { - field.init = field.default; - delete field.default; - } - } + if (params.watch != null) { + watchers ??= new Map(); - meta[type][fieldName] = field; - delete cluster[fieldName]; + for (const fieldWatcher of Array.toArray(params.watch)) { + if (Object.isPlainObject(fieldWatcher)) { + // FIXME: remove Object.cast + watchers.set(fieldWatcher.handler, Object.cast(normalizeFunctionalParams({...fieldWatcher}, meta))); - break; + } else { + // FIXME: remove Object.cast + watchers.set(fieldWatcher, Object.cast(normalizeFunctionalParams({handler: fieldWatcher}, meta))); } } + } - let field: ComponentField = meta[type][fieldName] ?? { - src: meta.componentName, - meta: {} - }; + field = normalizeFunctionalParams({ + ...field, + ...params, - let {watchers, after} = field; + after, + watchers, - if (params.after != null) { - after = new Set(Array.toArray(params.after)); + meta: { + ...field.meta, + ...params.meta } + }, meta); - if (params.watch != null) { - watchers ??= new Map(); + meta[type][fieldName] = field; - for (const fieldWatcher of Array.toArray(params.watch)) { - if (Object.isPlainObject(fieldWatcher)) { - // FIXME: remove Object.cast - watchers.set(fieldWatcher.handler, Object.cast(normalizeFunctionalParams({...fieldWatcher}, meta))); + if (field.init == null || !(INIT in field.init)) { + let getDefValue: CanNull = null; - } else { - // FIXME: remove Object.cast - watchers.set(fieldWatcher, Object.cast(normalizeFunctionalParams({handler: fieldWatcher}, meta))); - } - } + if (field.default !== undefined) { + getDefValue = () => field.default; } - field = normalizeFunctionalParams({ - ...field, - ...params, - - after, - watchers, - - meta: { - ...field.meta, - ...params.meta - } - }, meta); - - meta[type][fieldName] = field; - - if (field.init == null || !(INIT in field.init)) { - let getDefValue: CanNull = null; - - if (field.default !== undefined) { - getDefValue = () => field.default; - - } else if (meta.instance[fieldName] !== undefined) { - const defaultInstanceValue = meta.instance[fieldName]; - - if (Object.isPrimitive(defaultInstanceValue)) { - getDefValue = () => defaultInstanceValue; - - } else { - // To prevent linking to the same type of component for non-primitive values, - // it's important to clone the default value from the component constructor. - getDefValue = () => Object.fastClone(defaultInstanceValue); - } - } - - if (field.init != null) { - const customInit = field.init; - - field.init = (ctx, store) => { - const val = customInit(ctx, store); - - if (val === undefined && getDefValue != null) { - if (store[fieldName] === undefined) { - return getDefValue(); - } - - return undefined; - } + if (field.init != null) { + const customInit = field.init; - return val; - }; + field.init = (ctx, store) => { + const val = customInit(ctx, store); - } else if (getDefValue != null) { - field.init = (_, store) => { + if (val === undefined && getDefValue != null) { if (store[fieldName] === undefined) { - return getDefValue!(); + return getDefValue(); } return undefined; - }; - } - } + } - if (field.init != null) { - Object.defineProperty(field.init, INIT, {value: true}); - } + return val; + }; - if (isStore.test(fieldName)) { - const tiedWith = isStore.replace(fieldName); - meta.tiedFields[fieldName] = tiedWith; - meta.tiedFields[tiedWith] = fieldName; + } else if (getDefValue != null) { + field.init = (_, store) => { + if (store[fieldName] === undefined) { + return getDefValue!(); + } + + return undefined; + }; } + } - if (watchers != null && watchers.size > 0) { - meta.metaInitializers.set(fieldName, (meta) => { - const isFunctional = meta.params.functional === true; + if (field.init != null) { + Object.defineProperty(field.init, INIT, {value: true}); + } - for (const watcher of watchers!.values()) { - if (isFunctional && watcher.functional === false) { - continue; - } + if (isStore.test(fieldName)) { + const tiedWith = isStore.replace(fieldName); + meta.tiedFields[fieldName] = tiedWith; + meta.tiedFields[tiedWith] = fieldName; + } - const watcherListeners = meta.watchers[fieldName] ?? []; - meta.watchers[fieldName] = watcherListeners; + if (watchers != null && watchers.size > 0) { + meta.metaInitializers.set(fieldName, (meta) => { + const isFunctional = meta.params.functional === true; - watcherListeners.push(watcher); + for (const watcher of watchers!.values()) { + if (isFunctional && watcher.functional === false) { + continue; } - }); - } - }); + + const watcherListeners = meta.watchers[fieldName] ?? []; + meta.watchers[fieldName] = watcherListeners; + + watcherListeners.push(watcher); + } + }); + } } diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index 679308488c..1d471b5b56 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -6,8 +6,6 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { DEFAULT_WRAPPER } from 'core/component/const'; - import type { ComponentInterface } from 'core/component/interface'; import type { InitPropsObjectOptions } from 'core/component/prop/interface'; @@ -76,7 +74,7 @@ export function initProps( if (propValue === undefined && prop.default !== undefined) { propValue = prop.default; - if (Object.isFunction(propValue) && (opts.saveToStore === true || propValue[DEFAULT_WRAPPER] !== true)) { + if (Object.isFunction(propValue) && opts.saveToStore) { propValue = prop.type === Function ? propValue : propValue(component); if (Object.isFunction(propValue)) { From d146329f5268129ea9619e6b7ea9868ed2756c39 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 14:49:06 +0300 Subject: [PATCH 229/334] feat: added a transformer for registering default values on component properties --- build/ts-transformers/index.js | 5 +- .../resister-component-default-values.js | 176 ++++++++++++++++++ .../decorators/default-value/decorator.ts | 2 +- .../decorators/default-value/index.ts | 14 ++ 4 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 build/ts-transformers/resister-component-default-values.js create mode 100644 src/core/component/decorators/default-value/index.ts diff --git a/build/ts-transformers/index.js b/build/ts-transformers/index.js index bb25e93d79..31fe1120d8 100644 --- a/build/ts-transformers/index.js +++ b/build/ts-transformers/index.js @@ -9,7 +9,8 @@ 'use strict'; const - setComponentLayer = include('build/ts-transformers/set-component-layer'); + setComponentLayer = include('build/ts-transformers/set-component-layer'), + resisterComponentDefaultValues = include('build/ts-transformers/resister-component-default-values'); /** * Returns a settings object for setting up TypeScript transformers @@ -18,7 +19,7 @@ const * @returns {object} */ module.exports = (program) => ({ - before: [setComponentLayer(program)], + before: [setComponentLayer(program), resisterComponentDefaultValues], after: {}, afterDeclarations: {} }); diff --git a/build/ts-transformers/resister-component-default-values.js b/build/ts-transformers/resister-component-default-values.js new file mode 100644 index 0000000000..8223aad269 --- /dev/null +++ b/build/ts-transformers/resister-component-default-values.js @@ -0,0 +1,176 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +'use strict'; + +const ts = require('typescript'); + +/** + * @typedef {import('typescript').Transformer} Transformer + * @typedef {import('typescript').TransformationContext} TransformationContext + * @typedef {import('typescript').Node} Node + */ + +module.exports = resisterComponentDefaultValues; + +/** + * Registers default values for the properties of a class that is a component. + * The registration of default values is achieved through the defaultValue decorator. + * + * @param {TransformationContext} context + * @returns {Transformer} + * + * @example + * + * ```typescript + * class bExample { + * @prop(Array) + * prop = []; + * } + * ``` + * + * Will become: + * + * ```typescript + * class bExample { + * @defaultValue(() => []) + * @prop(Array) + * prop = []; + * } + * ``` + */ +function resisterComponentDefaultValues(context) { + let needImportDecorator = false; + + function visitor(node) { + if (ts.isPropertyDeclaration(node) && ts.hasInitializer(node) && isComponentClass(node.parent, 'component')) { + needImportDecorator = true; + return addDefaultValueDecorator(context, node); + } + + return ts.visitEachChild(node, visitor, context); + } + + return (node) => { + node = ts.visitNode(node, visitor); + + if (needImportDecorator) { + return addDefaultValueDecoratorImport(context, node); + } + + return node; + }; +} + +/** + * Adds the @defaultValue decorator for the specified class property + * + * @param {TransformationContext} context - the transformation context + * @param {Node} node - the property node in the AST + * @returns {Node} + */ +function addDefaultValueDecorator(context, node) { + const {factory} = context; + + const defaultValue = ts.getEffectiveInitializer(node); + + const getterValue = factory.createBlock( + [factory.createReturnStatement(defaultValue)], + true + ); + + const getter = factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + undefined, + getterValue + ); + + const decoratorExpr = factory.createCallExpression( + factory.createIdentifier('defaultValue'), + undefined, + [getter] + ); + + const decorator = factory.createDecorator(decoratorExpr); + + const decorators = factory.createNodeArray([decorator, ...(node.decorators || [])]); + + return factory.updatePropertyDeclaration( + node, + decorators, + node.modifiers, + node.name, + node.questionToken, + node.type, + node.initializer + ); +} + +/** + * Adds the import for the @defaultValue decorator to the specified file + * + * @param {TransformationContext} context - the transformation context + * @param {Node} node - the source file node in the AST + * @returns {Node} + */ +function addDefaultValueDecoratorImport(context, node) { + const {factory} = context; + + const decoratorSrc = factory.createStringLiteral('core/component/decorators/default-value'); + + const importSpecifier = factory.createImportSpecifier( + undefined, + undefined, + factory.createIdentifier('defaultValue') + ); + + const importClause = factory.createImportClause( + undefined, + undefined, + factory.createNamedImports([importSpecifier]) + ); + + const importDeclaration = factory.createImportDeclaration( + undefined, + undefined, + importClause, + decoratorSrc + ); + + const updatedStatements = factory.createNodeArray([ + importDeclaration, + ...node.statements + ]); + + return factory.updateSourceFile(node, updatedStatements); +} + +/** + * Returns true if the specified class is a component + * + * @param {Node} node - the class node in the AST + * @returns {boolean} + */ +function isComponentClass(node) { + const {decorators} = node; + + const getDecoratorName = (decorator) => ( + decorator.expression && + decorator.expression.expression && + ts.getEscapedTextOfIdentifierOrLiteral(decorator.expression.expression) + ); + + if (decorators != null && decorators.length > 0) { + return decorators.some((item) => getDecoratorName(item) === 'component'); + } + + return false; +} diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index 3bbc7186cb..bc8a307801 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -43,7 +43,7 @@ export function defaultValue(getter: () => unknown): PartDecorator { } else if (key in meta.fields) { regField(key, 'fields', {init: getter}, meta); - } else { + } else if (key in meta.systemFields) { regField(key, 'systemFields', {init: getter}, meta); } }); diff --git a/src/core/component/decorators/default-value/index.ts b/src/core/component/decorators/default-value/index.ts new file mode 100644 index 0000000000..7819c16673 --- /dev/null +++ b/src/core/component/decorators/default-value/index.ts @@ -0,0 +1,14 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +/** + * [[include:core/component/decorators/default-value/README.md]] + * @packageDocumentation + */ + +export * from 'core/component/decorators/default-value/decorator'; From 19e5de84e9d780e1df874c513c823c065596922b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 15:02:43 +0300 Subject: [PATCH 230/334] docs: added doc --- .../CHANGELOG.md | 16 +++++++ .../README.md | 43 +++++++++++++++++++ .../index.js} | 2 +- 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 build/ts-transformers/resister-component-default-values/CHANGELOG.md create mode 100644 build/ts-transformers/resister-component-default-values/README.md rename build/ts-transformers/{resister-component-default-values.js => resister-component-default-values/index.js} (99%) diff --git a/build/ts-transformers/resister-component-default-values/CHANGELOG.md b/build/ts-transformers/resister-component-default-values/CHANGELOG.md new file mode 100644 index 0000000000..6fe986f267 --- /dev/null +++ b/build/ts-transformers/resister-component-default-values/CHANGELOG.md @@ -0,0 +1,16 @@ +Changelog +========= + +> **Tags:** +> - :boom: [Breaking Change] +> - :rocket: [New Feature] +> - :bug: [Bug Fix] +> - :memo: [Documentation] +> - :house: [Internal] +> - :nail_care: [Polish] + +## v4.0.0-beta.?? (2024-??-??) + +#### :rocket: New Feature + +* Initial release diff --git a/build/ts-transformers/resister-component-default-values/README.md b/build/ts-transformers/resister-component-default-values/README.md new file mode 100644 index 0000000000..0def55297c --- /dev/null +++ b/build/ts-transformers/resister-component-default-values/README.md @@ -0,0 +1,43 @@ +# build/ts-transformers/resister-component-default-values + +This module provides a transformer for extracting default properties of a component class into a special decorator `@defaultValue` (`core/component/decorators/default-value`). + +This is necessary to allow retrieval of the default value for a component's prop at runtime without needing to create an instance of the component class. + +## Example + +```typescript +import iBlock, { prop } from 'components/super/i-block/i-block'; + +class bExample extends iBlock { + @prop(Array) + prop: string[] = []; +} +``` + +Will transform to + +```typescript +import { defaultValue } from 'core/component/decorators/default-value'; +import iBlock, { prop } from 'components/super/i-block/i-block'; + +class bExample extends iBlock { + @defaultValue(() => { return []; }) + @prop(Array) + prop: string[] = []; +} +``` + +## How to Attach the Transformer? + +To attach the transformer, you need to add its import to `build/ts-transformers`. + +```js +const registerComponentDefaultValues = include('build/ts-transformers/register-component-default-values'); + +module.exports = (program) => ({ + before: [registerComponentDefaultValues], + after: {}, + afterDeclarations: {} +}); +``` diff --git a/build/ts-transformers/resister-component-default-values.js b/build/ts-transformers/resister-component-default-values/index.js similarity index 99% rename from build/ts-transformers/resister-component-default-values.js rename to build/ts-transformers/resister-component-default-values/index.js index 8223aad269..f82528aef2 100644 --- a/build/ts-transformers/resister-component-default-values.js +++ b/build/ts-transformers/resister-component-default-values/index.js @@ -34,7 +34,7 @@ module.exports = resisterComponentDefaultValues; * } * ``` * - * Will become: + * Will transform to * * ```typescript * class bExample { From fb0f7404d987e5501511547fd11814d1feb1f241 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 15:22:52 +0300 Subject: [PATCH 231/334] docs: improved doc --- build/ts-transformers/CHANGELOG.md | 6 + build/ts-transformers/README.md | 10 +- build/ts-transformers/index.js | 6 +- .../README.md | 6 +- .../index.js | 30 ++-- build/ts-transformers/set-component-layer.js | 149 ------------------ .../set-component-layer/CHANGELOG.md | 16 ++ .../set-component-layer/README.md | 37 +++++ .../set-component-layer/index.js | 141 +++++++++++++++++ 9 files changed, 234 insertions(+), 167 deletions(-) delete mode 100644 build/ts-transformers/set-component-layer.js create mode 100644 build/ts-transformers/set-component-layer/CHANGELOG.md create mode 100644 build/ts-transformers/set-component-layer/README.md create mode 100644 build/ts-transformers/set-component-layer/index.js diff --git a/build/ts-transformers/CHANGELOG.md b/build/ts-transformers/CHANGELOG.md index 3a4087b7be..39ad4c3acc 100644 --- a/build/ts-transformers/CHANGELOG.md +++ b/build/ts-transformers/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :rocket: New Feature + +* Added a new transformer `resister-component-default-values` + ## v3.23.5 (2022-07-12) #### :rocket: New Feature diff --git a/build/ts-transformers/README.md b/build/ts-transformers/README.md index 5d68f868f4..5d487880ce 100644 --- a/build/ts-transformers/README.md +++ b/build/ts-transformers/README.md @@ -1,3 +1,11 @@ # build/ts-transformers -This module provides a bunch of custom transformers for TypeScript. \ No newline at end of file +This module provides a bunch of custom transformers for TypeScript/TSC. + +## Default Transformers + +* `set-component-layer` - this module provides a transformer that adds information to each component declaration about + the application layer in which the component is declared. + +* `resister-component-default-values` - this module provides a transformer for extracting default properties + of a component class into a special decorator `@defaultValue` (`core/component/decorators/default-value`). diff --git a/build/ts-transformers/index.js b/build/ts-transformers/index.js index 31fe1120d8..a7d977929b 100644 --- a/build/ts-transformers/index.js +++ b/build/ts-transformers/index.js @@ -14,12 +14,10 @@ const /** * Returns a settings object for setting up TypeScript transformers - * - * @param {import('typescript').Program} program * @returns {object} */ -module.exports = (program) => ({ - before: [setComponentLayer(program), resisterComponentDefaultValues], +module.exports = () => ({ + before: [setComponentLayer, resisterComponentDefaultValues], after: {}, afterDeclarations: {} }); diff --git a/build/ts-transformers/resister-component-default-values/README.md b/build/ts-transformers/resister-component-default-values/README.md index 0def55297c..2d6324dc3d 100644 --- a/build/ts-transformers/resister-component-default-values/README.md +++ b/build/ts-transformers/resister-component-default-values/README.md @@ -7,8 +7,9 @@ This is necessary to allow retrieval of the default value for a component's prop ## Example ```typescript -import iBlock, { prop } from 'components/super/i-block/i-block'; +import iBlock, { component, prop } from 'components/super/i-block/i-block'; +@component() class bExample extends iBlock { @prop(Array) prop: string[] = []; @@ -19,8 +20,9 @@ Will transform to ```typescript import { defaultValue } from 'core/component/decorators/default-value'; -import iBlock, { prop } from 'components/super/i-block/i-block'; +import iBlock, { component, prop } from 'components/super/i-block/i-block'; +@component() class bExample extends iBlock { @defaultValue(() => { return []; }) @prop(Array) diff --git a/build/ts-transformers/resister-component-default-values/index.js b/build/ts-transformers/resister-component-default-values/index.js index f82528aef2..5c2d078a3c 100644 --- a/build/ts-transformers/resister-component-default-values/index.js +++ b/build/ts-transformers/resister-component-default-values/index.js @@ -28,7 +28,8 @@ module.exports = resisterComponentDefaultValues; * @example * * ```typescript - * class bExample { + * @component() + * class bExample extends iBlock { * @prop(Array) * prop = []; * } @@ -37,7 +38,8 @@ module.exports = resisterComponentDefaultValues; * Will transform to * * ```typescript - * class bExample { + * @component() + * class bExample extends iBlock { * @defaultValue(() => []) * @prop(Array) * prop = []; @@ -47,15 +49,6 @@ module.exports = resisterComponentDefaultValues; function resisterComponentDefaultValues(context) { let needImportDecorator = false; - function visitor(node) { - if (ts.isPropertyDeclaration(node) && ts.hasInitializer(node) && isComponentClass(node.parent, 'component')) { - needImportDecorator = true; - return addDefaultValueDecorator(context, node); - } - - return ts.visitEachChild(node, visitor, context); - } - return (node) => { node = ts.visitNode(node, visitor); @@ -65,6 +58,21 @@ function resisterComponentDefaultValues(context) { return node; }; + + /** + * A visitor for the AST node + * + * @param {Node} node + * @returns {Node} + */ + function visitor(node) { + if (ts.isPropertyDeclaration(node) && ts.hasInitializer(node) && isComponentClass(node.parent, 'component')) { + needImportDecorator = true; + return addDefaultValueDecorator(context, node); + } + + return ts.visitEachChild(node, visitor, context); + } } /** diff --git a/build/ts-transformers/set-component-layer.js b/build/ts-transformers/set-component-layer.js deleted file mode 100644 index 31097ca027..0000000000 --- a/build/ts-transformers/set-component-layer.js +++ /dev/null @@ -1,149 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -/* eslint-disable capitalized-comments */ - -'use strict'; - -const ts = require('typescript'); -const {validators} = require('@pzlr/build-core'); - -/** - * @typedef {import('typescript').TransformationContext} Context - * @typedef {import('typescript').Node} Node - * @typedef {import('typescript').VisitResult} VisitResult - * @typedef {import('typescript').Transformer} Transformer - */ - -const - pathToRootRgxp = /(?.+)[/\\]src[/\\]/, - isComponentPath = new RegExp(`\\/(${validators.blockTypeList.join('|')})-.+?\\/?`); - -/** - * The transformer that adds the "layer" property to component-meta objects - * to indicate the name of the package in which it is defined - * - * @param {Context} context - * @returns {Transformer} - * @example - * ```typescript - * @component() - * class bExample {} - * - * // Becomes - * @component({ layer: '@v4fire/client' }) - * class bExample {} - * ``` - * - * ``` - * @component({functional: true}) - * class bExample {} - * - * // Becomes - * @component({ - * functional: true, - * layer: '@v4fire/client' - * }) - * class bExample {} - * ``` - */ -const setComponentLayerTransformer = (context) => (sourceFile) => { - if (!isInsideComponent(sourceFile.path)) { - return sourceFile; - } - - const layer = getLayerName(sourceFile.path); - const {factory} = context; - - /** - * A visitor for the AST node - * - * @param {Node} node - * @returns {Node} - */ - const visitor = (node) => { - if (ts.isDecorator(node) && isComponentCallExpression(node)) { - const - expr = node.expression; - - if (!ts.isCallExpression(expr)) { - return node; - } - - // noinspection JSAnnotator - const properties = expr.arguments?.[0]?.properties ?? []; - - const updatedCallExpression = factory.updateCallExpression( - expr, - expr.expression, - expr.typeArguments, - - [ - factory.createObjectLiteralExpression( - [ - ...properties, - factory.createPropertyAssignment( - factory.createIdentifier('layer'), - factory.createStringLiteral(layer) - ) - ], - - false - ) - ] - ); - - return factory.updateDecorator(node, updatedCallExpression); - } - - return ts.visitEachChild(node, visitor, context); - }; - - return ts.visitNode(sourceFile, visitor); -}; - -// eslint-disable-next-line @v4fire/require-jsdoc -module.exports = () => setComponentLayerTransformer; - -/** - * The function determines the package in which the module is defined and - * returns the name of this package from the `package.json` file - * - * @param {string} filePath - * @returns {string} - */ -function getLayerName(filePath) { - const pathToRootDir = filePath.match(pathToRootRgxp).groups.path; - return require(`${pathToRootDir}/package.json`).name; -} - -/** - * Returns true if the specified path is within the context of the component - * - * @param {string} filePath - * @returns {boolean} - */ -function isInsideComponent(filePath) { - return isComponentPath.test(filePath); -} - -/** - * Returns true if the specified call expression is `component()` - * - * @param {Node} node - * @returns {boolean} - */ -function isComponentCallExpression(node) { - const expr = node.expression; - - if (Boolean(expr) && !ts.isCallExpression(expr) || !ts.isIdentifier(expr?.expression)) { - return false; - } - - return expr.expression.escapedText === 'component'; -} diff --git a/build/ts-transformers/set-component-layer/CHANGELOG.md b/build/ts-transformers/set-component-layer/CHANGELOG.md new file mode 100644 index 0000000000..3a4087b7be --- /dev/null +++ b/build/ts-transformers/set-component-layer/CHANGELOG.md @@ -0,0 +1,16 @@ +Changelog +========= + +> **Tags:** +> - :boom: [Breaking Change] +> - :rocket: [New Feature] +> - :bug: [Bug Fix] +> - :memo: [Documentation] +> - :house: [Internal] +> - :nail_care: [Polish] + +## v3.23.5 (2022-07-12) + +#### :rocket: New Feature + +* Initial release diff --git a/build/ts-transformers/set-component-layer/README.md b/build/ts-transformers/set-component-layer/README.md new file mode 100644 index 0000000000..ae4d3eb03f --- /dev/null +++ b/build/ts-transformers/set-component-layer/README.md @@ -0,0 +1,37 @@ +# build/ts-transformers/set-component-layer + +This module provides a transformer that adds information to each component declaration about the application layer +in which the component is declared. +This is necessary for the correct functioning of component overrides in child layers. + +## Example + +```typescript +import iBlock, { component } from 'components/super/i-block/i-block'; + +@component() +class bExample extends iBlock {} +``` + +Will transform to + +```typescript +import iBlock, { component, prop } from 'components/super/i-block/i-block'; + +@component({layer: '@v4fire/client'}) +class bExample extends iBlock {} +``` + +## How to Attach the Transformer? + +To attach the transformer, you need to add its import to `build/ts-transformers`. + +```js +const setComponentLayer = include('build/ts-transformers/set-component-layer'); + +module.exports = (program) => ({ + before: [setComponentLayer], + after: {}, + afterDeclarations: {} +}); +``` diff --git a/build/ts-transformers/set-component-layer/index.js b/build/ts-transformers/set-component-layer/index.js new file mode 100644 index 0000000000..9638ee3932 --- /dev/null +++ b/build/ts-transformers/set-component-layer/index.js @@ -0,0 +1,141 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +/* eslint-disable capitalized-comments */ + +'use strict'; + +const ts = require('typescript'); +const {validators} = require('@pzlr/build-core'); + +/** + * @typedef {import('typescript').TransformationContext} Context + * @typedef {import('typescript').Node} Node + * @typedef {import('typescript').VisitResult} VisitResult + * @typedef {import('typescript').Transformer} Transformer + */ + +const + pathToRootRgxp = /(?.+)[/\\]src[/\\]/, + isComponentPath = new RegExp(`\\/(${validators.blockTypeList.join('|')})-.+?\\/?`); + +module.setComponentLayer = setComponentLayer; + +/** + * Adds the "layer" property to the component declaration parameters + * to indicate the name of the package in which it is defined + * + * @param {Context} context + * @returns {Transformer} + * + * @example + * ```typescript + * @component() + * class bExample extends iBlock {} + * ``` + * + * Will transform to + * + * ```typescript + * @component({layer: '@v4fire/client'}) + * class bExample extends iBlock {} + * ``` + */ +function setComponentLayer(context) { + return (sourceFile) => { + if (!isInsideComponent(sourceFile.path)) { + return sourceFile; + } + + const layer = getLayerName(sourceFile.path); + const {factory} = context; + + return ts.visitNode(sourceFile, visitor); + + /** + * A visitor for the AST node + * + * @param {Node} node + * @returns {Node} + */ + function visitor(node) { + if (ts.isDecorator(node) && isComponentCallExpression(node)) { + const expr = node.expression; + + if (!ts.isCallExpression(expr)) { + return node; + } + + // noinspection JSAnnotator + const properties = expr.arguments?.[0]?.properties ?? []; + + const updatedCallExpression = factory.updateCallExpression( + expr, + expr.expression, + expr.typeArguments, + + [ + factory.createObjectLiteralExpression( + [ + ...properties, + factory.createPropertyAssignment( + factory.createIdentifier('layer'), + factory.createStringLiteral(layer) + ) + ], + + false + ) + ] + ); + + return factory.updateDecorator(node, updatedCallExpression); + } + + return ts.visitEachChild(node, visitor, context); + } + }; +} + +/** + * The function determines the package in which the module is defined and + * returns the name of that package from the `package.json` file + * + * @param {string} filePath + * @returns {string} + */ +function getLayerName(filePath) { + const pathToRootDir = filePath.match(pathToRootRgxp).groups.path; + return require(`${pathToRootDir}/package.json`).name; +} + +/** + * Returns true if the specified path is within the component's context + * + * @param {string} filePath + * @returns {boolean} + */ +function isInsideComponent(filePath) { + return isComponentPath.test(filePath); +} + +/** + * Returns true if the specified call expression is `component()` + * + * @param {Node} node + * @returns {boolean} + */ +function isComponentCallExpression(node) { + const expr = node.expression; + + if (Boolean(expr) && !ts.isCallExpression(expr) || !ts.isIdentifier(expr?.expression)) { + return false; + } + + return expr.expression.escapedText === 'component'; +} From 854e5a78c96ca4a70086a076c9b9dfbc004509dd Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 15:25:03 +0300 Subject: [PATCH 232/334] chore: fixed a typo --- build/ts-transformers/set-component-layer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ts-transformers/set-component-layer/index.js b/build/ts-transformers/set-component-layer/index.js index 9638ee3932..e806f889de 100644 --- a/build/ts-transformers/set-component-layer/index.js +++ b/build/ts-transformers/set-component-layer/index.js @@ -24,7 +24,7 @@ const pathToRootRgxp = /(?.+)[/\\]src[/\\]/, isComponentPath = new RegExp(`\\/(${validators.blockTypeList.join('|')})-.+?\\/?`); -module.setComponentLayer = setComponentLayer; +module.exports = setComponentLayer; /** * Adds the "layer" property to the component declaration parameters From 76e45c74676d8c8ac53128d0106818920cf2edb3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 15:48:43 +0300 Subject: [PATCH 233/334] docs: improved doc --- .../resister-component-default-values/index.js | 5 +++++ .../set-component-layer/index.js | 4 ++++ src/core/component/decorators/README.md | 2 +- .../decorators/default-value/README.md | 18 ++++++++++++++++++ src/core/component/decorators/watch/README.md | 2 +- 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 src/core/component/decorators/default-value/README.md diff --git a/build/ts-transformers/resister-component-default-values/index.js b/build/ts-transformers/resister-component-default-values/index.js index 5c2d078a3c..7bb5bf6f03 100644 --- a/build/ts-transformers/resister-component-default-values/index.js +++ b/build/ts-transformers/resister-component-default-values/index.js @@ -28,6 +28,8 @@ module.exports = resisterComponentDefaultValues; * @example * * ```typescript + * import iBlock, { component, prop } from 'components/super/i-block/i-block'; + * * @component() * class bExample extends iBlock { * @prop(Array) @@ -38,6 +40,9 @@ module.exports = resisterComponentDefaultValues; * Will transform to * * ```typescript + * import { defaultValue } from 'core/component/decorators/default-value'; + * import iBlock, { component, prop } from 'components/super/i-block/i-block'; + * * @component() * class bExample extends iBlock { * @defaultValue(() => []) diff --git a/build/ts-transformers/set-component-layer/index.js b/build/ts-transformers/set-component-layer/index.js index e806f889de..b2c6abc6f6 100644 --- a/build/ts-transformers/set-component-layer/index.js +++ b/build/ts-transformers/set-component-layer/index.js @@ -35,6 +35,8 @@ module.exports = setComponentLayer; * * @example * ```typescript + * import iBlock, { component } from 'components/super/i-block/i-block'; + * * @component() * class bExample extends iBlock {} * ``` @@ -42,6 +44,8 @@ module.exports = setComponentLayer; * Will transform to * * ```typescript + * import iBlock, { component } from 'components/super/i-block/i-block'; + * * @component({layer: '@v4fire/client'}) * class bExample extends iBlock {} * ``` diff --git a/src/core/component/decorators/README.md b/src/core/component/decorators/README.md index 77cf922aac..e3d3856125 100644 --- a/src/core/component/decorators/README.md +++ b/src/core/component/decorators/README.md @@ -22,6 +22,6 @@ export default class bUser extends iBlock { * `@prop` to declare a component's input property (aka "prop"); * `@field` to declare a component's field; * `@system` to declare a component's system field (system field mutations never cause components to re-render); -* `@computed` to attach meta information to a component's computed field or accessor; +* `@computed` to attach meta-information to a component's computed field or accessor; * `@hook` to attach a hook listener; * `@watch` to attach a watcher. diff --git a/src/core/component/decorators/default-value/README.md b/src/core/component/decorators/default-value/README.md new file mode 100644 index 0000000000..52ff410e07 --- /dev/null +++ b/src/core/component/decorators/default-value/README.md @@ -0,0 +1,18 @@ +# core/component/decorators/default-value + +The decorator sets a default value for any prop or field of a component. +The value is set using a getter function. + +Typically, this decorator does not need to be used explicitly, +as it will be automatically added in the appropriate places during the build process. + +```typescript +import iBlock, { component, prop, defaultValue } from 'components/super/i-block/i-block'; + +@component() +class bExample extends iBlock { + @defaultValue(() => 0) + @prop(Number) + bla!: number; +} +``` diff --git a/src/core/component/decorators/watch/README.md b/src/core/component/decorators/watch/README.md index ec7784653a..a8a2bb1bdd 100644 --- a/src/core/component/decorators/watch/README.md +++ b/src/core/component/decorators/watch/README.md @@ -112,7 +112,7 @@ class bExample extends iBlock { } ``` -## Additional options +## Additional Options The `@watch` decorator can accept any options compatible with the watch function from the `core/object/watch` module. For a more detailed list of these options, please refer to that module's documentation. From 8b4c3254041012a9ed4c015bc8a61bc4ae9e6131 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 16:29:51 +0300 Subject: [PATCH 234/334] refactor: prefer constructor --- src/components/base/b-dynamic-page/b-dynamic-page.ts | 5 ++++- src/components/base/b-list/b-list.ts | 3 +-- src/components/base/b-router/b-router.ts | 3 +-- src/components/form/b-checkbox/b-checkbox.ts | 6 ++---- src/components/form/b-select/b-select.ts | 3 +-- src/components/friends/daemons/class.ts | 2 +- src/components/friends/state/class.ts | 7 +------ src/components/super/i-block/base/index.ts | 4 +++- src/components/super/i-block/event/index.ts | 2 +- .../super/i-block/modules/listeners/index.ts | 7 ++++--- src/components/super/i-block/providers/index.ts | 5 ++++- src/components/super/i-block/state/README.md | 3 +-- src/components/super/i-block/state/index.ts | 4 ++-- src/components/super/i-input/fields.ts | 2 +- src/components/super/i-input/i-input.ts | 8 +++++--- .../component/decorators/default-value/decorator.ts | 12 ++++++++++++ src/core/component/decorators/hook/README.md | 3 +-- src/core/component/init/states/before-create.ts | 6 ++++++ 18 files changed, 51 insertions(+), 34 deletions(-) diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index 4dc0366e4f..a636373e0a 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -542,7 +542,10 @@ export default class bDynamicPage extends iDynamicPage { protected override initBaseAPI(): void { super.initBaseAPI(); - this.addClearListenersToCache = this.instance.addClearListenersToCache.bind(this); + + const i = (this.constructor).prototype; + + this.addClearListenersToCache = i.addClearListenersToCache.bind(this); } /** diff --git a/src/components/base/b-list/b-list.ts b/src/components/base/b-list/b-list.ts index b7cacc090b..f6d9e81f62 100644 --- a/src/components/base/b-list/b-list.ts +++ b/src/components/base/b-list/b-list.ts @@ -302,8 +302,7 @@ class bList extends iListProps implements iVisible, iWidth, iActiveItems { protected override initBaseAPI(): void { super.initBaseAPI(); - const - i = this.instance; + const i = (this.constructor).prototype; this.isActive = i.isActive.bind(this); this.setActive = i.setActive.bind(this); diff --git a/src/components/base/b-router/b-router.ts b/src/components/base/b-router/b-router.ts index 570f4c21bf..7bab4f4010 100644 --- a/src/components/base/b-router/b-router.ts +++ b/src/components/base/b-router/b-router.ts @@ -424,8 +424,7 @@ export default class bRouter extends iRouterProps { protected override initBaseAPI(): void { super.initBaseAPI(); - const - i = this.instance; + const i = (this.constructor).prototype; this.compileStaticRoutes = i.compileStaticRoutes.bind(this); this.emitTransition = i.emitTransition.bind(this); diff --git a/src/components/form/b-checkbox/b-checkbox.ts b/src/components/form/b-checkbox/b-checkbox.ts index 3759106403..ca21b0bb3c 100644 --- a/src/components/form/b-checkbox/b-checkbox.ts +++ b/src/components/form/b-checkbox/b-checkbox.ts @@ -241,8 +241,7 @@ export default class bCheckbox extends iInput implements iSize { protected override initBaseAPI(): void { super.initBaseAPI(); - const - i = this.instance; + const i = (this.constructor).prototype; this.convertValueToChecked = i.convertValueToChecked.bind(this); this.onCheckedChange = i.onCheckedChange.bind(this); @@ -286,8 +285,7 @@ export default class bCheckbox extends iInput implements iSize { } protected override resolveValue(value?: this['Value']): this['Value'] { - const - i = this.instance; + const i = (this.constructor).prototype; const canApplyDefault = value === undefined && diff --git a/src/components/form/b-select/b-select.ts b/src/components/form/b-select/b-select.ts index 85399a1f8b..1dfc6984fe 100644 --- a/src/components/form/b-select/b-select.ts +++ b/src/components/form/b-select/b-select.ts @@ -442,8 +442,7 @@ class bSelect extends iSelectProps implements iOpenToggle, iActiveItems { protected override initBaseAPI(): void { super.initBaseAPI(); - const - i = this.instance; + const i = (this.constructor).prototype; this.normalizeItems = i.normalizeItems.bind(this); } diff --git a/src/components/friends/daemons/class.ts b/src/components/friends/daemons/class.ts index 456db1a296..d3371c5989 100644 --- a/src/components/friends/daemons/class.ts +++ b/src/components/friends/daemons/class.ts @@ -25,7 +25,7 @@ class Daemons extends Friend { * A dictionary with the declared component daemons */ protected get daemons(): WrappedDaemonsDict { - return Object.cast((this.ctx.instance.constructor).daemons); + return Object.cast((this.ctx.constructor).daemons); } init(): void { diff --git a/src/components/friends/state/class.ts b/src/components/friends/state/class.ts index 33af4723e8..e7e47e7442 100644 --- a/src/components/friends/state/class.ts +++ b/src/components/friends/state/class.ts @@ -47,12 +47,7 @@ class State extends Friend { get needRouterSync(): boolean { // @ts-ignore (access) baseSyncRouterState ??= iBlock.prototype.syncRouterState; - return baseSyncRouterState !== Object.cast(this.instance).syncRouterState; - } - - /** {@link iBlock.instance} */ - protected get instance(): this['CTX']['instance'] { - return this.ctx.instance; + return baseSyncRouterState !== this.ctx.constructor.prototype.syncRouterState; } } diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index b75695e775..7a98e595c8 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -673,7 +673,9 @@ export default abstract class iBlockBase extends iBlockFriends { */ @hook('beforeRuntime') protected initBaseAPI(): void { - this.watch = this.instance.watch.bind(this); + const i = (this.constructor).prototype; + + this.watch = i.watch.bind(this); if (this.getParent != null) { const {$parent} = this; diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index 4280a29e67..af97ba85a4 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -597,7 +597,7 @@ export default abstract class iBlockEvent extends iBlockBase { protected override initBaseAPI(): void { super.initBaseAPI(); - const i = this.instance; + const i = (this.constructor).prototype; this.on = i.on.bind(this); this.once = i.once.bind(this); diff --git a/src/components/super/i-block/modules/listeners/index.ts b/src/components/super/i-block/modules/listeners/index.ts index bb1885280f..93fe87a599 100644 --- a/src/components/super/i-block/modules/listeners/index.ts +++ b/src/components/super/i-block/modules/listeners/index.ts @@ -31,13 +31,14 @@ export function initGlobalListeners(component: iBlock, resetListener?: boolean): // eslint-disable-next-line @v4fire/unbound-method baseInitLoad ??= iBlock.prototype.initLoad; - const - ctx = component.unsafe; + const ctx = component.unsafe; const { async: $a, + globalName, globalEmitter: $e, + state: $s, state: {needRouterSync} } = ctx; @@ -45,7 +46,7 @@ export function initGlobalListeners(component: iBlock, resetListener?: boolean): $e.once(`destroy.${ctx.remoteState.appProcessId}`, ctx.$destroy.bind(ctx)); resetListener = Boolean( - (resetListener ?? baseInitLoad !== ctx.instance.initLoad) || + (resetListener ?? baseInitLoad !== ctx.constructor.prototype.initLoad) || (globalName ?? needRouterSync) ); diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 66d1952304..1068e464fd 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -368,6 +368,9 @@ export default abstract class iBlockProviders extends iBlockState { protected override initBaseAPI(): void { super.initBaseAPI(); - this.createDataProviderInstance = this.instance.createDataProviderInstance.bind(this); + + const i = (this.constructor).prototype; + + this.createDataProviderInstance = i.createDataProviderInstance.bind(this); } } diff --git a/src/components/super/i-block/state/README.md b/src/components/super/i-block/state/README.md index 691eaa2b0a..0438df191b 100644 --- a/src/components/super/i-block/state/README.md +++ b/src/components/super/i-block/state/README.md @@ -38,8 +38,7 @@ the [[iBlock]] class incorporates the following code. ``` @hook('beforeRuntime') protected initBaseAPI() { - const - i = this.instance; + const i = this.constructor.prototype; this.syncStorageState = i.syncStorageState.bind(this); this.syncRouterState = i.syncRouterState.bind(this); diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index f9ad390d3d..1724fee1fc 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -212,7 +212,7 @@ export default abstract class iBlockState extends iBlockMods { value === 'ready' && oldValue === 'beforeReady' || value === 'inactive' && !this.renderOnActivation || - (this.instance.constructor).shadowComponentStatuses[value]; + (this.constructor).shadowComponentStatuses[value]; if (isShadowStatus) { this.shadowComponentStatusStore = value; @@ -602,7 +602,7 @@ export default abstract class iBlockState extends iBlockMods { protected override initBaseAPI(): void { super.initBaseAPI(); - const i = this.instance; + const i = (this.constructor).prototype; this.i18n = i.i18n.bind(this); this.syncStorageState = i.syncStorageState.bind(this); diff --git a/src/components/super/i-input/fields.ts b/src/components/super/i-input/fields.ts index ccc4d0de2b..86024c2f3d 100644 --- a/src/components/super/i-input/fields.ts +++ b/src/components/super/i-input/fields.ts @@ -71,7 +71,7 @@ export default abstract class iInputFields extends iInputProps { * A map of available component validators */ get validatorsMap(): typeof iInputFields['validators'] { - return (this.instance.constructor).validators; + return (this.constructor).validators; } /** diff --git a/src/components/super/i-input/i-input.ts b/src/components/super/i-input/i-input.ts index a5f101fbdc..1bfb1c2752 100644 --- a/src/components/super/i-input/i-input.ts +++ b/src/components/super/i-input/i-input.ts @@ -334,8 +334,7 @@ export default abstract class iInput extends iInputHandlers implements iVisible, * @param [value] */ protected resolveValue(value?: this['Value']): this['Value'] { - const - i = this.instance; + const i = (this.constructor).prototype; if (value === undefined && this.lfc.isBeforeCreate()) { return i['defaultGetter'].call(this); @@ -365,6 +364,9 @@ export default abstract class iInput extends iInputHandlers implements iVisible, protected override initBaseAPI(): void { super.initBaseAPI(); - this.resolveValue = this.instance.resolveValue.bind(this); + + const i = (this.constructor).prototype; + + this.resolveValue = i.resolveValue.bind(this); } } diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index bc8a307801..14a3833e82 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -45,6 +45,18 @@ export function defaultValue(getter: () => unknown): PartDecorator { } else if (key in meta.systemFields) { regField(key, 'systemFields', {init: getter}, meta); + + } else { + const value = getter(); + + if (Object.isFunction(value)) { + Object.defineProperty(meta.constructor.prototype, key, { + configurable: true, + enumerable: false, + writable: true, + value + }); + } } }); } diff --git a/src/core/component/decorators/hook/README.md b/src/core/component/decorators/hook/README.md index 52379c2fe5..e431772fd1 100644 --- a/src/core/component/decorators/hook/README.md +++ b/src/core/component/decorators/hook/README.md @@ -55,8 +55,7 @@ However, to use some methods before the `created` hook, the [[iBlock]] class has ``` @hook('beforeRuntime') protected initBaseAPI() { - const - i = this.instance; + const i = this.constructor.prototype; this.syncStorageState = i.syncStorageState.bind(this); this.syncRouterState = i.syncRouterState.bind(this); diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index d5bd4b2a46..739164deff 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -65,6 +65,12 @@ export function beforeCreateState( get: () => meta.instance }); + Object.defineProperty(unsafe, 'constructor', { + configurable: true, + enumerable: true, + value: meta.constructor + }); + unsafe.$fields = {}; unsafe.$systemFields = {}; unsafe.$modifiedFields = {}; From d34170e0b5aa348f7f637fc81ddce59d6056637a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 16:56:52 +0300 Subject: [PATCH 235/334] chore: optimized default values --- .../index.js | 47 ++++++++++++++----- .../decorators/default-value/README.md | 12 +++-- .../decorators/default-value/decorator.ts | 40 ++++++++-------- .../component/decorators/system/decorator.ts | 14 ++---- 4 files changed, 68 insertions(+), 45 deletions(-) diff --git a/build/ts-transformers/resister-component-default-values/index.js b/build/ts-transformers/resister-component-default-values/index.js index 7bb5bf6f03..3787ad065c 100644 --- a/build/ts-transformers/resister-component-default-values/index.js +++ b/build/ts-transformers/resister-component-default-values/index.js @@ -92,19 +92,40 @@ function addDefaultValueDecorator(context, node) { const defaultValue = ts.getEffectiveInitializer(node); - const getterValue = factory.createBlock( - [factory.createReturnStatement(defaultValue)], - true - ); - - const getter = factory.createArrowFunction( - undefined, - undefined, - [], - undefined, - undefined, - getterValue - ); + let getter; + + if (ts.isFunctionLike(defaultValue)) { + getter = defaultValue; + + } else if ( + ts.isNumericLiteral(defaultValue) || + ts.isBigIntLiteral(defaultValue) || + ts.isStringLiteral(defaultValue) || + defaultValue.kind === ts.SyntaxKind.UndefinedKeyword || + defaultValue.kind === ts.SyntaxKind.NullKeyword || + defaultValue.kind === ts.SyntaxKind.TrueKeyword || + defaultValue.kind === ts.SyntaxKind.FalseKeyword + ) { + getter = defaultValue; + + } else { + const getterValue = ts.isFunctionLike(defaultValue) ? + defaultValue : + + factory.createBlock( + [factory.createReturnStatement(defaultValue)], + true + ); + + getter = factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + undefined, + getterValue + ); + } const decoratorExpr = factory.createCallExpression( factory.createIdentifier('defaultValue'), diff --git a/src/core/component/decorators/default-value/README.md b/src/core/component/decorators/default-value/README.md index 52ff410e07..609e3ffa9f 100644 --- a/src/core/component/decorators/default-value/README.md +++ b/src/core/component/decorators/default-value/README.md @@ -1,18 +1,22 @@ # core/component/decorators/default-value The decorator sets a default value for any prop or field of a component. -The value is set using a getter function. Typically, this decorator does not need to be used explicitly, as it will be automatically added in the appropriate places during the build process. ```typescript -import iBlock, { component, prop, defaultValue } from 'components/super/i-block/i-block'; +import { defaultValue } from 'core/component/decorators/default-value'; +import iBlock, { component, prop, system } from 'components/super/i-block/i-block'; @component() class bExample extends iBlock { - @defaultValue(() => 0) + @defaultValue(0) @prop(Number) - bla!: number; + id!: number; + + @defaultValue(() => ({})) + @system() + opts: Dictionary; } ``` diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index 14a3833e82..80d1f2b9b9 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -15,7 +15,6 @@ import type { PartDecorator } from 'core/component/decorators/interface'; /** * Sets a default value for the specified prop or component field. - * The value is set using a getter function. * * Typically, this decorator does not need to be used explicitly, * as it will be automatically added in the appropriate places during the build process. @@ -25,38 +24,41 @@ import type { PartDecorator } from 'core/component/decorators/interface'; * * @example * ```typescript - * import iBlock, { component, prop, defaultValue } from 'components/super/i-block/i-block'; + * import { defaultValue } from 'core/component/decorators/default-value'; + * import iBlock, { component, prop, system } from 'components/super/i-block/i-block'; * * @component() * class bExample extends iBlock { - * @defaultValue(() => 0) + * @defaultValue(0) * @prop(Number) - * bla!: number; + * id!: number; + * + * @defaultValue(() => ({})) + * @system() + * opts: Dictionary; * } * ``` */ -export function defaultValue(getter: () => unknown): PartDecorator { +export function defaultValue(getter: unknown): PartDecorator { return createComponentDecorator(({meta}, key) => { + const isFunction = Object.isFunction(getter); + if (key in meta.props) { regProp(key, {default: getter}, meta); } else if (key in meta.fields) { - regField(key, 'fields', {init: getter}, meta); + regField(key, 'fields', isFunction ? {init: getter} : {default: getter}, meta); } else if (key in meta.systemFields) { - regField(key, 'systemFields', {init: getter}, meta); - - } else { - const value = getter(); - - if (Object.isFunction(value)) { - Object.defineProperty(meta.constructor.prototype, key, { - configurable: true, - enumerable: false, - writable: true, - value - }); - } + regField(key, 'systemFields', isFunction ? {init: getter} : {default: getter}, meta); + + } else if (isFunction) { + Object.defineProperty(meta.constructor.prototype, key, { + configurable: true, + enumerable: false, + writable: true, + value: getter + }); } }); } diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index b02a5d520e..a25639da8e 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -157,11 +157,7 @@ export function regField( meta[type][fieldName] = field; if (field.init == null || !(INIT in field.init)) { - let getDefValue: CanNull = null; - - if (field.default !== undefined) { - getDefValue = () => field.default; - } + const defValue = field.default; if (field.init != null) { const customInit = field.init; @@ -169,9 +165,9 @@ export function regField( field.init = (ctx, store) => { const val = customInit(ctx, store); - if (val === undefined && getDefValue != null) { + if (val === undefined && defValue !== undefined) { if (store[fieldName] === undefined) { - return getDefValue(); + return defValue; } return undefined; @@ -180,10 +176,10 @@ export function regField( return val; }; - } else if (getDefValue != null) { + } else if (defValue !== undefined) { field.init = (_, store) => { if (store[fieldName] === undefined) { - return getDefValue!(); + return defValue; } return undefined; From 1fe0b8248ae141565defdb22b001ca7d45779e7c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 16 Oct 2024 18:10:25 +0300 Subject: [PATCH 236/334] fix: fixed issues with static properties --- .../resister-component-default-values/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build/ts-transformers/resister-component-default-values/index.js b/build/ts-transformers/resister-component-default-values/index.js index 3787ad065c..f4c369ef9e 100644 --- a/build/ts-transformers/resister-component-default-values/index.js +++ b/build/ts-transformers/resister-component-default-values/index.js @@ -71,7 +71,12 @@ function resisterComponentDefaultValues(context) { * @returns {Node} */ function visitor(node) { - if (ts.isPropertyDeclaration(node) && ts.hasInitializer(node) && isComponentClass(node.parent, 'component')) { + if ( + ts.isPropertyDeclaration(node) && + ts.hasInitializer(node) && + !node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword) && + isComponentClass(node.parent, 'component') + ) { needImportDecorator = true; return addDefaultValueDecorator(context, node); } From be529463e33f2455ef46baad1a6ade76bf25a4ec Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 16:46:46 +0300 Subject: [PATCH 237/334] refactor: improved DSL transformer --- build/ts-transformers/README.md | 3 +- build/ts-transformers/index.js | 6 +- .../CHANGELOG.md | 0 .../README.md | 32 ++++- .../index.js | 122 ++++++++++++++++-- 5 files changed, 141 insertions(+), 22 deletions(-) rename build/ts-transformers/{resister-component-default-values => resister-component-parts}/CHANGELOG.md (100%) rename build/ts-transformers/{resister-component-default-values => resister-component-parts}/README.md (54%) rename build/ts-transformers/{resister-component-default-values => resister-component-parts}/index.js (60%) diff --git a/build/ts-transformers/README.md b/build/ts-transformers/README.md index 5d487880ce..62b7e5d68c 100644 --- a/build/ts-transformers/README.md +++ b/build/ts-transformers/README.md @@ -7,5 +7,4 @@ This module provides a bunch of custom transformers for TypeScript/TSC. * `set-component-layer` - this module provides a transformer that adds information to each component declaration about the application layer in which the component is declared. -* `resister-component-default-values` - this module provides a transformer for extracting default properties - of a component class into a special decorator `@defaultValue` (`core/component/decorators/default-value`). +* `resister-component-parts` - this module provides a transformer for registering parts of a class as parts of the associated component. diff --git a/build/ts-transformers/index.js b/build/ts-transformers/index.js index a7d977929b..fb40c85839 100644 --- a/build/ts-transformers/index.js +++ b/build/ts-transformers/index.js @@ -10,14 +10,14 @@ const setComponentLayer = include('build/ts-transformers/set-component-layer'), - resisterComponentDefaultValues = include('build/ts-transformers/resister-component-default-values'); + resisterComponentParts = include('build/ts-transformers/resister-component-parts'); /** - * Returns a settings object for setting up TypeScript transformers + * Returns a settings object for configuring TypeScript transformers * @returns {object} */ module.exports = () => ({ - before: [setComponentLayer, resisterComponentDefaultValues], + before: [setComponentLayer, resisterComponentParts], after: {}, afterDeclarations: {} }); diff --git a/build/ts-transformers/resister-component-default-values/CHANGELOG.md b/build/ts-transformers/resister-component-parts/CHANGELOG.md similarity index 100% rename from build/ts-transformers/resister-component-default-values/CHANGELOG.md rename to build/ts-transformers/resister-component-parts/CHANGELOG.md diff --git a/build/ts-transformers/resister-component-default-values/README.md b/build/ts-transformers/resister-component-parts/README.md similarity index 54% rename from build/ts-transformers/resister-component-default-values/README.md rename to build/ts-transformers/resister-component-parts/README.md index 2d6324dc3d..b137eaf2d3 100644 --- a/build/ts-transformers/resister-component-default-values/README.md +++ b/build/ts-transformers/resister-component-parts/README.md @@ -1,8 +1,6 @@ -# build/ts-transformers/resister-component-default-values +# build/ts-transformers/resister-component-parts -This module provides a transformer for extracting default properties of a component class into a special decorator `@defaultValue` (`core/component/decorators/default-value`). - -This is necessary to allow retrieval of the default value for a component's prop at runtime without needing to create an instance of the component class. +This module provides a transformer for registering parts of a class as parts of the associated component. ## Example @@ -13,6 +11,14 @@ import iBlock, { component, prop } from 'components/super/i-block/i-block'; class bExample extends iBlock { @prop(Array) prop: string[] = []; + + get answer() { + return 42; + } + + just() { + return 'do it'; + } } ``` @@ -20,6 +26,8 @@ Will transform to ```typescript import { defaultValue } from 'core/component/decorators/default-value'; +import { method } from 'core/component/decorators/method'; + import iBlock, { component, prop } from 'components/super/i-block/i-block'; @component() @@ -27,6 +35,16 @@ class bExample extends iBlock { @defaultValue(() => { return []; }) @prop(Array) prop: string[] = []; + + @method('accessor') + get answer() { + return 42; + } + + @method('method') + just() { + return 'do it'; + } } ``` @@ -35,10 +53,10 @@ class bExample extends iBlock { To attach the transformer, you need to add its import to `build/ts-transformers`. ```js -const registerComponentDefaultValues = include('build/ts-transformers/register-component-default-values'); +const resisterComponentParts = include('build/ts-transformers/resister-component-parts'); -module.exports = (program) => ({ - before: [registerComponentDefaultValues], +module.exports = () => ({ + before: [resisterComponentParts], after: {}, afterDeclarations: {} }); diff --git a/build/ts-transformers/resister-component-default-values/index.js b/build/ts-transformers/resister-component-parts/index.js similarity index 60% rename from build/ts-transformers/resister-component-default-values/index.js rename to build/ts-transformers/resister-component-parts/index.js index f4c369ef9e..7b3f5e3628 100644 --- a/build/ts-transformers/resister-component-default-values/index.js +++ b/build/ts-transformers/resister-component-parts/index.js @@ -19,8 +19,8 @@ const ts = require('typescript'); module.exports = resisterComponentDefaultValues; /** - * Registers default values for the properties of a class that is a component. - * The registration of default values is achieved through the defaultValue decorator. + * Registers parts of a class as parts of the associated component. + * For example, all methods and accessors of the class are registered as methods and accessors of the component. * * @param {TransformationContext} context * @returns {Transformer} @@ -34,6 +34,14 @@ module.exports = resisterComponentDefaultValues; * class bExample extends iBlock { * @prop(Array) * prop = []; + * + * get answer() { + * return 42; + * } + * + * just() { + * return 'do it'; + * } * } * ``` * @@ -41,6 +49,8 @@ module.exports = resisterComponentDefaultValues; * * ```typescript * import { defaultValue } from 'core/component/decorators/default-value'; + * import { method } from 'core/component/decorators/method'; + * * import iBlock, { component, prop } from 'components/super/i-block/i-block'; * * @component() @@ -48,17 +58,33 @@ module.exports = resisterComponentDefaultValues; * @defaultValue(() => []) * @prop(Array) * prop = []; + * + * @method('accessor') + * get answer() { + * return 42; + * } + * + * @method('method') + * just() { + * return 'do it'; + * } * } * ``` */ function resisterComponentDefaultValues(context) { - let needImportDecorator = false; + let + needImportDefaultValueDecorator = false, + needImportMethodDecorator = false; return (node) => { node = ts.visitNode(node, visitor); - if (needImportDecorator) { - return addDefaultValueDecoratorImport(context, node); + if (needImportDefaultValueDecorator) { + node = addDecoratorImport('defaultValue', 'core/component/decorators/default-value', context, node); + } + + if (needImportMethodDecorator) { + node = addDecoratorImport('method', 'core/component/decorators/method', context, node); } return node; @@ -77,10 +103,18 @@ function resisterComponentDefaultValues(context) { !node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword) && isComponentClass(node.parent, 'component') ) { - needImportDecorator = true; + needImportDefaultValueDecorator = true; return addDefaultValueDecorator(context, node); } + if ( + (ts.isMethodDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) && + isComponentClass(node.parent, 'component') + ) { + needImportMethodDecorator = true; + return addMethodDecorator(context, node); + } + return ts.visitEachChild(node, visitor, context); } } @@ -154,21 +188,89 @@ function addDefaultValueDecorator(context, node) { } /** - * Adds the import for the @defaultValue decorator to the specified file + * Adds the @method decorator for the specified class method or accessor + * + * @param {TransformationContext} context - the transformation context + * @param {Node} node - the method/accessor node in the AST + * @returns {Node} + */ +function addMethodDecorator(context, node) { + const {factory} = context; + + let type; + + if (ts.isMethodDeclaration(node)) { + type = 'method'; + + } else { + type = 'accessor'; + } + + const decoratorExpr = factory.createCallExpression( + factory.createIdentifier('method'), + undefined, + [factory.createStringLiteral(type)] + ); + + const decorator = factory.createDecorator(decoratorExpr); + + const decorators = factory.createNodeArray([decorator, ...(node.decorators || [])]); + + if (ts.isMethodDeclaration(node)) { + return factory.updateMethodDeclaration( + node, + decorators, + node.modifiers, + node.asteriskToken, + node.name, + node.questionToken, + node.typeParameters, + node.parameters, + node.type, + node.body + ); + } + + if (ts.isGetAccessorDeclaration(node)) { + return factory.updateGetAccessorDeclaration( + node, + decorators, + node.modifiers, + node.name, + node.parameters, + node.type, + node.body + ); + } + + return factory.updateSetAccessorDeclaration( + node, + decorators, + node.modifiers, + node.name, + node.parameters, + node.body + ); +} + +/** + * Adds the import for the decorator with the specified name to the specified file * + * @param {string} name - the name of the decorator to be imported and applied (e.g., `defaultValue`) + * @param {string} path - the path from which the decorator should be imported (e.g., `core/component/decorators`) * @param {TransformationContext} context - the transformation context * @param {Node} node - the source file node in the AST * @returns {Node} */ -function addDefaultValueDecoratorImport(context, node) { +function addDecoratorImport(name, path, context, node) { const {factory} = context; - const decoratorSrc = factory.createStringLiteral('core/component/decorators/default-value'); + const decoratorSrc = factory.createStringLiteral(path); const importSpecifier = factory.createImportSpecifier( undefined, undefined, - factory.createIdentifier('defaultValue') + factory.createIdentifier(name) ); const importClause = factory.createImportClause( From 1274c413881672da6db0e211dad97c37f0b3087f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 16:46:59 +0300 Subject: [PATCH 238/334] chore: improved doc --- build/ts-transformers/set-component-layer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ts-transformers/set-component-layer/README.md b/build/ts-transformers/set-component-layer/README.md index ae4d3eb03f..0695e2a2dd 100644 --- a/build/ts-transformers/set-component-layer/README.md +++ b/build/ts-transformers/set-component-layer/README.md @@ -29,7 +29,7 @@ To attach the transformer, you need to add its import to `build/ts-transformers` ```js const setComponentLayer = include('build/ts-transformers/set-component-layer'); -module.exports = (program) => ({ +module.exports = () => ({ before: [setComponentLayer], after: {}, afterDeclarations: {} From 1026cbe5ba9efc305db9ce5d02065bdf01ea898f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 17:25:41 +0300 Subject: [PATCH 239/334] chore: optimized dsl decorators --- .../decorators/computed/decorator.ts | 8 +-- .../decorators/default-value/decorator.ts | 6 +-- src/core/component/decorators/helpers.ts | 54 ++++++++++++++----- .../component/decorators/hook/decorator.ts | 8 +-- src/core/component/decorators/interface.ts | 8 ++- .../component/decorators/prop/decorator.ts | 6 +-- .../component/decorators/system/decorator.ts | 6 +-- .../component/decorators/watch/decorator.ts | 4 +- 8 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts index 7694d878f1..094b456ba7 100644 --- a/src/core/component/decorators/computed/decorator.ts +++ b/src/core/component/decorators/computed/decorator.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; +import { createComponentDecorator3, normalizeFunctionalParams } from 'core/component/decorators/helpers'; import type { ComponentAccessor } from 'core/component/interface'; @@ -34,11 +34,7 @@ import type { DecoratorComputed } from 'core/component/decorators/computed/inter * ``` */ export function computed(params?: DecoratorComputed): PartDecorator { - return createComponentDecorator(({meta}, accessorName, desc) => { - if (desc == null) { - return; - } - + return createComponentDecorator3(({meta}, accessorName) => { params = {...params}; delete meta.props[accessorName]; diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index 80d1f2b9b9..8bc442450e 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -9,7 +9,7 @@ import { regProp } from 'core/component/decorators/prop'; import { regField } from 'core/component/decorators/system'; -import { createComponentDecorator } from 'core/component/decorators/helpers'; +import { createComponentDecorator3 } from 'core/component/decorators/helpers'; import type { PartDecorator } from 'core/component/decorators/interface'; @@ -20,7 +20,7 @@ import type { PartDecorator } from 'core/component/decorators/interface'; * as it will be automatically added in the appropriate places during the build process. * * @decorator - * @param [getter] - a function that returns the default value for a prop or field. + * @param [getter] - a function that returns the default value for a prop or field * * @example * ```typescript @@ -40,7 +40,7 @@ import type { PartDecorator } from 'core/component/decorators/interface'; * ``` */ export function defaultValue(getter: unknown): PartDecorator { - return createComponentDecorator(({meta}, key) => { + return createComponentDecorator3(({meta}, key) => { const isFunction = Object.isFunction(getter); if (key in meta.props) { diff --git a/src/core/component/decorators/helpers.ts b/src/core/component/decorators/helpers.ts index bf7a8a27ae..0da9ad09d6 100644 --- a/src/core/component/decorators/helpers.ts +++ b/src/core/component/decorators/helpers.ts @@ -15,7 +15,9 @@ import type { ComponentMeta } from 'core/component/meta'; import type { PartDecorator, - ComponentPartDecorator, + + ComponentPartDecorator3, + ComponentPartDecorator4, ComponentDescriptor, DecoratorFunctionalOptions @@ -23,22 +25,50 @@ import type { } from 'core/component/decorators/interface'; /** - * Creates a decorator for a component's property or method based on the provided decorator function + * Creates a decorator for a component's property or method based on the provided decorator function. + * The decorator function expects three input arguments (excluding the object descriptor). + * + * @param decorator + */ +export function createComponentDecorator3(decorator: ComponentPartDecorator3): PartDecorator { + return (proto: object, partKey: string) => { + createComponentDecorator(decorator, partKey, undefined, proto); + }; +} + +/** + * Creates a decorator for a component's property or method based on the provided decorator function. + * The decorator function expects four input arguments (including the object descriptor). + * * @param decorator */ -export function createComponentDecorator(decorator: ComponentPartDecorator): PartDecorator { - return (_: object, partKey: string, partDesc?: PropertyDescriptor) => { - initEmitter.once('bindConstructor', (componentName: string, regEvent: string) => { - const decoratedKeys = componentDecoratedKeys[componentName] ?? new Set(); - componentDecoratedKeys[componentName] = decoratedKeys; +export function createComponentDecorator4(decorator: ComponentPartDecorator4): PartDecorator { + return (proto: object, partKey: string, partDesc?: PropertyDescriptor) => { + createComponentDecorator(decorator, partKey, partDesc, proto); + }; +} - decoratedKeys.add(partKey); +function createComponentDecorator( + decorator: ComponentPartDecorator3 | ComponentPartDecorator4, + partKey: string, + partDesc: CanUndef, + proto: object +): void { + initEmitter.once('bindConstructor', (componentName: string, regEvent: string) => { + const decoratedKeys = componentDecoratedKeys[componentName] ?? new Set(); + componentDecoratedKeys[componentName] = decoratedKeys; - initEmitter.once(regEvent, (componentDesc: ComponentDescriptor) => { - decorator(componentDesc, partKey, partDesc); - }); + decoratedKeys.add(partKey); + + initEmitter.once(regEvent, (componentDesc: ComponentDescriptor) => { + if (decorator.length <= 3) { + (decorator)(componentDesc, partKey, proto); + + } else { + (decorator)(componentDesc, partKey, partDesc, proto); + } }); - }; + }); } /** diff --git a/src/core/component/decorators/hook/decorator.ts b/src/core/component/decorators/hook/decorator.ts index 3a47008525..e23cc52442 100644 --- a/src/core/component/decorators/hook/decorator.ts +++ b/src/core/component/decorators/hook/decorator.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; +import { createComponentDecorator3, normalizeFunctionalParams } from 'core/component/decorators/helpers'; import type { ComponentMethod } from 'core/component/interface'; @@ -35,11 +35,7 @@ import type { DecoratorHook } from 'core/component/decorators/hook/interface'; * ``` */ export function hook(hook: DecoratorHook): PartDecorator { - return createComponentDecorator(({meta}, methodName, desc) => { - if (desc == null) { - return; - } - + return createComponentDecorator3(({meta}, methodName) => { const methodHooks = Array.toArray(hook); const method: ComponentMethod = meta.methods[methodName] ?? { diff --git a/src/core/component/decorators/interface.ts b/src/core/component/decorators/interface.ts index 82f955c7dd..ef8715f9be 100644 --- a/src/core/component/decorators/interface.ts +++ b/src/core/component/decorators/interface.ts @@ -21,8 +21,12 @@ export interface ComponentDescriptor { parentMeta: CanNull; } -export interface ComponentPartDecorator { - (component: ComponentDescriptor, partKey: string, partDesc?: PropertyDescriptor): void; +export interface ComponentPartDecorator3 { + (component: ComponentDescriptor, partKey: string, proto: object): void; +} + +export interface ComponentPartDecorator4 { + (component: ComponentDescriptor, partKey: string, partDesc: CanUndef, proto: object): void; } export interface PartDecorator { diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 2bae4b4abe..2c2c884218 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -9,7 +9,7 @@ import { defProp } from 'core/const/props'; import { isBinding } from 'core/component/reflect'; -import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; +import { createComponentDecorator3, normalizeFunctionalParams } from 'core/component/decorators/helpers'; import type { ComponentMeta } from 'core/component/meta'; import type { ComponentProp, ComponentField } from 'core/component/interface'; @@ -41,13 +41,13 @@ import type { DecoratorProp, PropType } from 'core/component/decorators/prop/int * ``` */ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { - return createComponentDecorator((desc, propName) => { + return createComponentDecorator3((desc, propName) => { regProp(propName, typeOrParams, desc.meta); }); } /** - * Registers a component prop to the specified metaobject + * Registers a component prop in the specified metaobject * * @param propName - the name of the property * @param typeOrParams - a constructor of the property type or an object with property parameters diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index a25639da8e..0e545a17b8 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -9,7 +9,7 @@ import { defProp } from 'core/const/props'; import { isStore } from 'core/component/reflect'; -import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; +import { createComponentDecorator3, normalizeFunctionalParams } from 'core/component/decorators/helpers'; import type { ComponentField } from 'core/component/interface'; import type { ComponentMeta } from 'core/component/meta'; @@ -56,13 +56,13 @@ export function system( initOrParams?: InitFieldFn | DecoratorSystem | DecoratorField, type: 'fields' | 'systemFields' = 'systemFields' ): PartDecorator { - return createComponentDecorator((desc, fieldName) => { + return createComponentDecorator3((desc, fieldName) => { regField(fieldName, type, initOrParams, desc.meta); }); } /** - * Registers a component field to the specified metaobject + * Registers a component field in the specified metaobject * * @param fieldName - the name of the field * @param type - the type of the registered field: `systemFields` or `fields` diff --git a/src/core/component/decorators/watch/decorator.ts b/src/core/component/decorators/watch/decorator.ts index 2f43646694..da79a9ea29 100644 --- a/src/core/component/decorators/watch/decorator.ts +++ b/src/core/component/decorators/watch/decorator.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { createComponentDecorator, normalizeFunctionalParams } from 'core/component/decorators/helpers'; +import { createComponentDecorator4, normalizeFunctionalParams } from 'core/component/decorators/helpers'; import type { ComponentProp, ComponentField, ComponentMethod } from 'core/component/interface'; @@ -128,7 +128,7 @@ import type { DecoratorFieldWatcher, DecoratorMethodWatcher } from 'core/compone * ``` */ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): PartDecorator { - return createComponentDecorator(({meta}, key, desc) => { + return createComponentDecorator4(({meta}, key, desc) => { if (desc == null) { decorateField(); From f1176ca0cb2f1b56d4cf4937e84a7be6df9df00a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 17:29:51 +0300 Subject: [PATCH 240/334] feat: added a new decorator `method` --- .../component/decorators/method/README.md | 24 ++ .../component/decorators/method/decorator.ts | 238 ++++++++++++++++++ src/core/component/decorators/method/index.ts | 14 ++ .../component/decorators/method/interface.ts | 9 + 4 files changed, 285 insertions(+) create mode 100644 src/core/component/decorators/method/README.md create mode 100644 src/core/component/decorators/method/decorator.ts create mode 100644 src/core/component/decorators/method/index.ts create mode 100644 src/core/component/decorators/method/interface.ts diff --git a/src/core/component/decorators/method/README.md b/src/core/component/decorators/method/README.md new file mode 100644 index 0000000000..6c48d54892 --- /dev/null +++ b/src/core/component/decorators/method/README.md @@ -0,0 +1,24 @@ +# core/component/decorators/method + +The decorator marks a class method or accessor as a component part. + +Typically, this decorator does not need to be used explicitly, +as it will be automatically added in the appropriate places during the build process. + +```typescript +import { method } from 'core/component/decorators/method'; +import iBlock, { component, prop, system } from 'components/super/i-block/i-block'; + +@component() +class bExample extends iBlock { + @method('accessor') + get answer() { + return 42; + } + + @method('method') + just() { + return 'do it'; + } +} +``` diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts new file mode 100644 index 0000000000..1ee816eaa6 --- /dev/null +++ b/src/core/component/decorators/method/decorator.ts @@ -0,0 +1,238 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +import { defProp } from 'core/const/props'; + +import { createComponentDecorator3 } from 'core/component/decorators/helpers'; + +import { getComponentContext } from 'core/component/context'; + +import type { ComponentMeta } from 'core/component/meta'; +import type { ComponentAccessor, ComponentMethod } from 'core/component/interface'; + +import type { PartDecorator } from 'core/component/decorators/interface'; +import type { MethodType } from 'core/component/decorators/method/interface'; + +export * from 'core/component/decorators/method/interface'; + +/** + * Marks a class method or accessor as a component part. + * + * Typically, this decorator does not need to be used explicitly, + * as it will be automatically added in the appropriate places during the build process. + * + * @decorator + * @param type - the type of the member: `method` or `accessor` + * + * @example + * ```typescript + * import { method } from 'core/component/decorators/method'; + * import iBlock, { component, prop, system } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @method('accessor') + * get answer() { + * return 42; + * } + * + * @method('method') + * just() { + * return 'do it'; + * } + * } + * ``` + */ +export function method(type: MethodType): PartDecorator { + return createComponentDecorator3((desc, name, proto) => { + regMethod(name, type, desc.meta, proto); + }); +} + +/** + * Registers a method or accessor in the specified metaobject + * + * @param name - the name of the method or accessor to be registered + * @param type - the type of the member: `method` or `accessor` + * @param meta - the metaobject where the member is registered + * @param proto - the prototype of the class where the method or accessor is defined + */ +export function regMethod(name: string, type: MethodType, meta: ComponentMeta, proto: object): void { + const { + component, + componentName: src, + + props, + fields, + systemFields, + + computedFields, + accessors, + methods, + + metaInitializers + } = meta; + + if (type === 'method') { + const method = proto[name]; + + const methodDesc: ComponentMethod = Object.assign(methods[name] ?? {watchers: {}, hooks: {}}, {src, fn: method}); + methods[name] = methodDesc; + + component.methods[name] = method; + + // eslint-disable-next-line func-style + const wrapper = function wrapper(this: object) { + // eslint-disable-next-line prefer-rest-params + return method.apply(getComponentContext(this), arguments); + }; + + if (wrapper.length !== method.length) { + Object.defineProperty(wrapper, 'length', {get: () => method.length}); + } + + component.methods[name] = wrapper; + + const + watchers = methodDesc.watchers != null ? Object.keys(methodDesc.watchers) : [], + hooks = methodDesc.hooks != null ? Object.keys(methodDesc.hooks) : []; + + if (watchers.length > 0 || hooks.length > 0) { + metaInitializers.set(name, (meta) => { + const isFunctional = meta.params.functional === true; + + for (const watcherName of watchers) { + const watcher = methodDesc.watchers![watcherName]; + + if (watcher == null || isFunctional && watcher.functional === false) { + continue; + } + + const watcherListeners = meta.watchers[watcherName] ?? []; + meta.watchers[watcherName] = watcherListeners; + + watcherListeners.push({ + ...watcher, + method: name, + args: Array.toArray(watcher.args), + handler: method + }); + } + + for (const hookName of hooks) { + const hook = methodDesc.hooks![hookName]; + + if (isFunctional && hook.functional === false) { + continue; + } + + meta.hooks[hookName].push({...hook, fn: method}); + } + }); + } + + } else { + const desc = Object.getOwnPropertyDescriptor(proto, name); + + if (desc == null) { + return; + } + + const + propKey = `${name}Prop`, + storeKey = `${name}Store`; + + let + type: 'accessors' | 'computedFields' = 'accessors', + tiedWith: CanNull = null; + + // Computed fields are cached by default + if ( + name in computedFields || + !(name in accessors) && (tiedWith = props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) + ) { + type = 'computedFields'; + } + + let field: Dictionary; + + if (props[name] != null) { + field = props; + + } else if (fields[name] != null) { + field = fields; + + } else { + field = systemFields; + } + + const store = meta[type]; + + // If we already have a property by this key, like a prop or field, + // we need to delete it to correct override + if (field[name] != null) { + Object.defineProperty(proto, name, defProp); + delete field[name]; + } + + const + old = store[name], + set = desc.set ?? old?.set, + get = desc.get ?? old?.get; + + // To use `super` within the setter, we also create a new method with a name `${key}Setter` + if (set != null) { + const methodName = `${name}Setter`; + proto[methodName] = set; + + meta.methods[methodName] = { + src, + fn: set, + watchers: {}, + hooks: {} + }; + } + + // To using `super` within the getter, we also create a new method with a name `${key}Getter` + if (get != null) { + const methodName = `${name}Getter`; + proto[methodName] = get; + + meta.methods[methodName] = { + src, + fn: get, + watchers: {}, + hooks: {} + }; + } + + const accessor: ComponentAccessor = Object.assign(store[name] ?? {cache: false}, { + src, + get, + set + }); + + store[name] = accessor; + + if (accessor.cache === 'auto') { + component.computed[name] = { + get: accessor.get, + set: accessor.set + }; + } + + // eslint-disable-next-line eqeqeq + if (accessor.functional === undefined && meta.params.functional === null) { + accessor.functional = false; + } + + if (tiedWith != null) { + accessor.tiedWith = tiedWith; + } + } +} diff --git a/src/core/component/decorators/method/index.ts b/src/core/component/decorators/method/index.ts new file mode 100644 index 0000000000..56719ae095 --- /dev/null +++ b/src/core/component/decorators/method/index.ts @@ -0,0 +1,14 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +/** + * [[include:core/component/decorators/method/README.md]] + * @packageDocumentation + */ + +export * from 'core/component/decorators/method/decorator'; diff --git a/src/core/component/decorators/method/interface.ts b/src/core/component/decorators/method/interface.ts new file mode 100644 index 0000000000..f47824c399 --- /dev/null +++ b/src/core/component/decorators/method/interface.ts @@ -0,0 +1,9 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +export type MethodType = 'method' | 'accessor'; From ec1b559ba8a74214f183013ca2ee680c800fde2b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 17:54:29 +0300 Subject: [PATCH 241/334] feat: added a new decorator @derive for components --- src/components/traits/index.ts | 155 +++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/components/traits/index.ts diff --git a/src/components/traits/index.ts b/src/components/traits/index.ts new file mode 100644 index 0000000000..b18b76d1c3 --- /dev/null +++ b/src/components/traits/index.ts @@ -0,0 +1,155 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +/** + * [[include:components/traits/README.md]] + * @packageDocumentation + */ + +import { initEmitter, ComponentDescriptor } from 'core/component'; +import { regMethod, MethodType } from 'core/component/decorators/method'; + +/** + * Derives the provided traits to a component. + * The function is used to organize multiple implementing interfaces with the support of default methods. + * + * @decorator + * @param traits + * + * @example + * ```typescript + * import { derive } from 'components/traits'; + * import iBlock, { component } from 'components/super/i-block/i-block'; + * + * abstract class iOpen { + * /** + * * This method has a default implementation. + * * The implementation is provided as a static method. + * *\/ + * open(): void { + * return Object.throw(); + * }; + * + * /** + * * The default implementation for iOpen.open. + * * The method takes a context as its first parameter. + * * + * * @see iOpen['open'] + * *\/ + * static open: AddSelf = (self) => { + * self.setMod('opened', true); + * }; + * } + * + * abstract class iSize { + * abstract sizes(): string[]; + * } + * + * interface bExample extends Trait, Trait { + * + * } + * + * @component() + * @derive(iOpen, iSize) + * class bExample extends iBlock implements iOpen, iSize { + * sizes() { + * return ['xs', 's', 'm', 'l', 'xl']; + * } + * } + * + * console.log(new bExample().open()); + * ``` + */ +export function derive(...traits: Function[]) { + return (target: Function): void => { + initEmitter.once('bindConstructor', (_: string, regEvent: string) => { + initEmitter.once(regEvent, ({meta}: ComponentDescriptor) => { + const proto = target.prototype; + + for (let i = 0; i < traits.length; i++) { + const + originalTrait = traits[i], + chain = getTraitChain(originalTrait); + + for (let i = 0; i < chain.length; i++) { + const [trait, keys] = chain[i]; + + for (let i = 0; i < keys.length; i++) { + const + key = keys[i], + defMethod = Object.getOwnPropertyDescriptor(trait, key), + traitMethod = Object.getOwnPropertyDescriptor(trait.prototype, key); + + const canDerive = + defMethod != null && + traitMethod != null && + !(key in proto) && + + Object.isFunction(defMethod.value) && ( + Object.isFunction(traitMethod.value) || + + // eslint-disable-next-line @v4fire/unbound-method + Object.isFunction(traitMethod.get) || Object.isFunction(traitMethod.set) + ); + + if (canDerive) { + let type: MethodType; + + const newDescriptor: PropertyDescriptor = { + enumerable: false, + configurable: true + }; + + if (Object.isFunction(traitMethod.value)) { + Object.assign(newDescriptor, { + writable: true, + + // eslint-disable-next-line func-name-matching + value: function defaultMethod(...args: unknown[]) { + return originalTrait[key](this, ...args); + } + }); + + type = 'method'; + + } else { + Object.assign(newDescriptor, { + get() { + return originalTrait[key](this); + }, + + set(value: unknown) { + originalTrait[key](this, value); + } + }); + + type = 'accessor'; + } + + Object.defineProperty(proto, key, newDescriptor); + regMethod(key, type, meta, proto); + } + } + } + } + + function getTraitChain>( + trait: Nullable, + methods: T = Object.cast([]) + ): T { + if (!Object.isFunction(trait) || trait === Function.prototype) { + return methods; + } + + methods.push([trait, Object.getOwnPropertyNames(trait)]); + return getTraitChain(Object.getPrototypeOf(trait), methods); + } + }); + }); + }; +} From 49072bc3d7bd9e20d61cd78e6f41f7abf47d10db Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 17:58:54 +0300 Subject: [PATCH 242/334] chore: use new @derive --- src/components/base/b-bottom-slide/b-bottom-slide.ts | 2 +- src/components/base/b-list/b-list.ts | 2 +- src/components/base/b-sidebar/b-sidebar.ts | 2 +- src/components/base/b-slider/b-slider.ts | 2 +- src/components/base/b-tree/b-tree.ts | 2 +- .../base/b-virtual-scroll-new/b-virtual-scroll-new.ts | 2 +- src/components/base/b-window/b-window.ts | 2 +- src/components/form/b-button/b-button.ts | 2 +- src/components/form/b-select/b-select.ts | 2 +- src/components/super/i-data/data.ts | 2 +- src/components/traits/README.md | 6 +++--- src/components/traits/i-access/README.md | 2 +- src/components/traits/i-active-items/README.md | 2 +- src/components/traits/i-control-list/README.md | 2 +- .../b-traits-i-control-list-dummy.ts | 2 +- src/components/traits/i-lock-page-scroll/README.md | 2 +- .../b-traits-i-lock-page-scroll-dummy.ts | 2 +- src/components/traits/i-observe-dom/README.md | 2 +- .../b-traits-i-observe-dom-dummy.ts | 2 +- src/components/traits/i-open-toggle/README.md | 2 +- src/components/traits/i-open/README.md | 2 +- 21 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/base/b-bottom-slide/b-bottom-slide.ts b/src/components/base/b-bottom-slide/b-bottom-slide.ts index 129192064b..db661feb31 100644 --- a/src/components/base/b-bottom-slide/b-bottom-slide.ts +++ b/src/components/base/b-bottom-slide/b-bottom-slide.ts @@ -14,7 +14,7 @@ import symbolGenerator from 'core/symbol'; import SyncPromise from 'core/promise/sync'; -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import History from 'components/traits/i-history/history'; import type iHistory from 'components/traits/i-history/i-history'; diff --git a/src/components/base/b-list/b-list.ts b/src/components/base/b-list/b-list.ts index f6d9e81f62..fd35943f8b 100644 --- a/src/components/base/b-list/b-list.ts +++ b/src/components/base/b-list/b-list.ts @@ -12,7 +12,7 @@ */ import SyncPromise from 'core/promise/sync'; -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import DOM, { delegateElement } from 'components/friends/dom'; import Block, { element, elements, setElementMod } from 'components/friends/block'; diff --git a/src/components/base/b-sidebar/b-sidebar.ts b/src/components/base/b-sidebar/b-sidebar.ts index 7ef848de07..e63f5df369 100644 --- a/src/components/base/b-sidebar/b-sidebar.ts +++ b/src/components/base/b-sidebar/b-sidebar.ts @@ -11,7 +11,7 @@ * @packageDocumentation */ -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import Block, { getElementSelector } from 'components/friends/block'; diff --git a/src/components/base/b-slider/b-slider.ts b/src/components/base/b-slider/b-slider.ts index ddf7d8cf45..86509a00f0 100644 --- a/src/components/base/b-slider/b-slider.ts +++ b/src/components/base/b-slider/b-slider.ts @@ -13,7 +13,7 @@ import symbolGenerator from 'core/symbol'; -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import iObserveDOM from 'components/traits/i-observe-dom/i-observe-dom'; import iItems, { IterationKey } from 'components/traits/i-items/i-items'; diff --git a/src/components/base/b-tree/b-tree.ts b/src/components/base/b-tree/b-tree.ts index 63d602cf27..62b603e4f4 100644 --- a/src/components/base/b-tree/b-tree.ts +++ b/src/components/base/b-tree/b-tree.ts @@ -14,7 +14,7 @@ import symbolGenerator from 'core/symbol'; import SyncPromise from 'core/promise/sync'; -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import AsyncRender, { iterate, TaskOptions } from 'components/friends/async-render'; import Block, { getElementMod, setElementMod, getElementSelector, getFullElementName } from 'components/friends/block'; diff --git a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts index cd3dca7998..6a3585fbfe 100644 --- a/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts +++ b/src/components/base/b-virtual-scroll-new/b-virtual-scroll-new.ts @@ -12,7 +12,7 @@ */ import symbolGenerator from 'core/symbol'; -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import type { AsyncOptions } from 'core/async'; import SyncPromise from 'core/promise/sync'; diff --git a/src/components/base/b-window/b-window.ts b/src/components/base/b-window/b-window.ts index 6c156f15e7..b9088fae54 100644 --- a/src/components/base/b-window/b-window.ts +++ b/src/components/base/b-window/b-window.ts @@ -12,7 +12,7 @@ */ import symbolGenerator from 'core/symbol'; -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import Block, { getElementSelector } from 'components/friends/block'; diff --git a/src/components/form/b-button/b-button.ts b/src/components/form/b-button/b-button.ts index 390eabd834..c056359449 100644 --- a/src/components/form/b-button/b-button.ts +++ b/src/components/form/b-button/b-button.ts @@ -11,7 +11,7 @@ * @packageDocumentation */ -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import DataProvider, { getDefaultRequestParams, base, get } from 'components/friends/data-provider'; import type bForm from 'components/form/b-form/b-form'; diff --git a/src/components/form/b-select/b-select.ts b/src/components/form/b-select/b-select.ts index 1dfc6984fe..b04cc8191e 100644 --- a/src/components/form/b-select/b-select.ts +++ b/src/components/form/b-select/b-select.ts @@ -13,7 +13,7 @@ import SyncPromise from 'core/promise/sync'; -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import Block, { setElementMod, removeElementMod, getElementSelector, element, elements } from 'components/friends/block'; import DOM, { delegateElement } from 'components/friends/dom'; diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index bae2b5190e..852e35848b 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -7,7 +7,7 @@ */ import symbolGenerator from 'core/symbol'; -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import type iData from 'components/super/i-data/i-data'; import type DataProvider from 'components/friends/data-provider'; diff --git a/src/components/traits/README.md b/src/components/traits/README.md index f79232b99b..228ce38631 100644 --- a/src/components/traits/README.md +++ b/src/components/traits/README.md @@ -99,7 +99,7 @@ We have defined a trait. We can now proceed to implement it in a basic class. specify any traits we want to automatically implement. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; interface DuckLike extends Trait {} @@ -116,7 +116,7 @@ We have defined a trait. We can now proceed to implement it in a basic class. 4. Profit! Now TS will automatically understand the methods of the interface, and they will work at runtime. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; interface DuckLike extends Trait {} @@ -136,7 +136,7 @@ We have defined a trait. We can now proceed to implement it in a basic class. 5. Of course, we can implement more than one trait in a component. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; interface DuckLike extends Trait, Trait {} diff --git a/src/components/traits/i-access/README.md b/src/components/traits/i-access/README.md index 691cd1369e..278df4ce7f 100644 --- a/src/components/traits/i-access/README.md +++ b/src/components/traits/i-access/README.md @@ -14,7 +14,7 @@ or disabling. * The trait can be automatically derived. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; import iAccess from 'components/traits/i-access/i-access'; import iBlock, { component } from 'components/super/i-block/i-block'; diff --git a/src/components/traits/i-active-items/README.md b/src/components/traits/i-active-items/README.md index 4855143645..5f956db8fe 100644 --- a/src/components/traits/i-active-items/README.md +++ b/src/components/traits/i-active-items/README.md @@ -14,7 +14,7 @@ Take a look at [[bTree]] or [[bList]] to see more. * The trait can be partially derived. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; import iActiveItems from 'traits/i-active-items/i-active-items'; import iBlock, { component } from 'components/super/i-block/i-block'; diff --git a/src/components/traits/i-control-list/README.md b/src/components/traits/i-control-list/README.md index 5c5a409014..2ac7f1240f 100644 --- a/src/components/traits/i-control-list/README.md +++ b/src/components/traits/i-control-list/README.md @@ -13,7 +13,7 @@ This module provides a trait with helpers for a component that renders a list of * The trait can be automatically derived. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; import iControlList, { Control } from 'components/traits/i-control-list/i-control-list'; import iBlock, { component } from 'components/super/i-block/i-block'; diff --git a/src/components/traits/i-control-list/test/b-traits-i-control-list-dummy/b-traits-i-control-list-dummy.ts b/src/components/traits/i-control-list/test/b-traits-i-control-list-dummy/b-traits-i-control-list-dummy.ts index c12aff6cec..e40d1c059e 100644 --- a/src/components/traits/i-control-list/test/b-traits-i-control-list-dummy/b-traits-i-control-list-dummy.ts +++ b/src/components/traits/i-control-list/test/b-traits-i-control-list-dummy/b-traits-i-control-list-dummy.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import iControlList, { Control } from 'components/traits/i-control-list/i-control-list'; import iBlock, { component, prop } from 'components/super/i-block/i-block'; diff --git a/src/components/traits/i-lock-page-scroll/README.md b/src/components/traits/i-lock-page-scroll/README.md index a5b6830a86..2913b46f51 100644 --- a/src/components/traits/i-lock-page-scroll/README.md +++ b/src/components/traits/i-lock-page-scroll/README.md @@ -12,7 +12,7 @@ It is useful if you have an issue with page scrolling under popups or other over * The trait can be automatically derived. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; import iLockPageScroll from 'components/traits/i-lock-page-scroll/i-lock-page-scroll'; import iBlock, { component, wait } from 'components/super/i-block/i-block'; diff --git a/src/components/traits/i-lock-page-scroll/test/b-traits-i-lock-page-scroll-dummy/b-traits-i-lock-page-scroll-dummy.ts b/src/components/traits/i-lock-page-scroll/test/b-traits-i-lock-page-scroll-dummy/b-traits-i-lock-page-scroll-dummy.ts index 3732d5daa2..81f2fd738b 100644 --- a/src/components/traits/i-lock-page-scroll/test/b-traits-i-lock-page-scroll-dummy/b-traits-i-lock-page-scroll-dummy.ts +++ b/src/components/traits/i-lock-page-scroll/test/b-traits-i-lock-page-scroll-dummy/b-traits-i-lock-page-scroll-dummy.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import bDummy, { component } from 'components/dummies/b-dummy/b-dummy'; import iLockPageScroll from 'components/traits/i-lock-page-scroll/i-lock-page-scroll'; diff --git a/src/components/traits/i-observe-dom/README.md b/src/components/traits/i-observe-dom/README.md index 48f6469ffd..6958ef45a2 100644 --- a/src/components/traits/i-observe-dom/README.md +++ b/src/components/traits/i-observe-dom/README.md @@ -11,7 +11,7 @@ This module provides a trait for a component to observe DOM changes by using [`M * The trait can be automatically derived. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; import iObserveDOM from 'components/traits/i-observe-dom/i-observe-dom'; import iBlock, { component, wait } from 'components/super/i-block/i-block'; diff --git a/src/components/traits/i-observe-dom/test/b-traits-i-observe-dom-dummy/b-traits-i-observe-dom-dummy.ts b/src/components/traits/i-observe-dom/test/b-traits-i-observe-dom-dummy/b-traits-i-observe-dom-dummy.ts index 7c84f19100..e059f4a2b3 100644 --- a/src/components/traits/i-observe-dom/test/b-traits-i-observe-dom-dummy/b-traits-i-observe-dom-dummy.ts +++ b/src/components/traits/i-observe-dom/test/b-traits-i-observe-dom-dummy/b-traits-i-observe-dom-dummy.ts @@ -6,7 +6,7 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { derive } from 'core/functools/trait'; +import { derive } from 'components/traits'; import bDummy, { component, hook, wait } from 'components/dummies/b-dummy/b-dummy'; import iObserveDOM from 'components/traits/i-observe-dom/i-observe-dom'; diff --git a/src/components/traits/i-open-toggle/README.md b/src/components/traits/i-open-toggle/README.md index 8b6367c0a0..b3cdc6d742 100644 --- a/src/components/traits/i-open-toggle/README.md +++ b/src/components/traits/i-open-toggle/README.md @@ -13,7 +13,7 @@ This module provides a trait for a component that extends the "opening/closing" * The trait can be automatically derived. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; import iOpenToggle from 'components/traits/i-open-toggle/i-open-toggle'; import iBlock, { component } from 'components/super/i-block/i-block'; diff --git a/src/components/traits/i-open/README.md b/src/components/traits/i-open/README.md index f90b6af1f7..ca96a123e1 100644 --- a/src/components/traits/i-open/README.md +++ b/src/components/traits/i-open/README.md @@ -11,7 +11,7 @@ This module provides a trait for a component that needs to implement the "openin * The trait can be automatically derived. ```typescript - import { derive } from 'core/functools/trait'; + import { derive } from 'components/traits'; import iOpen from 'components/traits/i-open/i-open'; import iBlock, { component } from 'components/super/i-block/i-block'; From 30db8c5fcfc5d75e20c022c958b837e60e97361a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 17:59:31 +0300 Subject: [PATCH 243/334] chore: removed `addMethodsToMeta` --- .../component/decorators/component/index.ts | 7 +- src/core/component/meta/README.md | 5 - src/core/component/meta/fill.ts | 3 - src/core/component/meta/index.ts | 1 - src/core/component/meta/method.ts | 218 ------------------ 5 files changed, 1 insertion(+), 233 deletions(-) delete mode 100644 src/core/component/meta/method.ts diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 291e239ad9..31e5d5c056 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -32,8 +32,7 @@ import { inheritMods, inheritParams, - attachTemplatesToMeta, - addMethodsToMeta + attachTemplatesToMeta } from 'core/component/meta'; @@ -103,10 +102,6 @@ export function component(opts?: ComponentOptions): Function { meta = createMeta(componentInfo); components.set(componentFullName, meta); } - - initEmitter.once(regEvent, () => { - addMethodsToMeta(components.get(componentFullName)!, target); - }); }); return; diff --git a/src/core/component/meta/README.md b/src/core/component/meta/README.md index 6085170a51..8db117ccb8 100644 --- a/src/core/component/meta/README.md +++ b/src/core/component/meta/README.md @@ -202,11 +202,6 @@ This function modifies the original object. Populates the passed metaobject with methods and properties from the specified component class constructor. -### addMethodsToMeta - -Loops through the prototype of the passed component constructor and -adds methods and accessors to the specified metaobject. - ### attachTemplatesToMeta Attaches templates to the specified metaobject. diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 8b0eabdbb0..4080c8e921 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -9,7 +9,6 @@ import { isAbstractComponent } from 'core/component/reflect'; import { sortFields } from 'core/component/meta/field'; -import { addMethodsToMeta } from 'core/component/meta/method'; import type { ComponentConstructor, ModVal } from 'core/component/interface'; import type { ComponentMeta } from 'core/component/meta/interface'; @@ -25,8 +24,6 @@ const * @param [constructor] - the component constructor */ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor = meta.constructor): ComponentMeta { - addMethodsToMeta(meta, constructor); - if (isAbstractComponent.test(meta.componentName)) { return meta; } diff --git a/src/core/component/meta/index.ts b/src/core/component/meta/index.ts index d9e97d9fb5..5d1378ff67 100644 --- a/src/core/component/meta/index.ts +++ b/src/core/component/meta/index.ts @@ -15,6 +15,5 @@ export * from 'core/component/meta/interface'; export * from 'core/component/meta/create'; export * from 'core/component/meta/fill'; export * from 'core/component/meta/fork'; -export * from 'core/component/meta/method'; export * from 'core/component/meta/tpl'; export * from 'core/component/meta/inherit'; diff --git a/src/core/component/meta/method.ts b/src/core/component/meta/method.ts deleted file mode 100644 index b6743b8da9..0000000000 --- a/src/core/component/meta/method.ts +++ /dev/null @@ -1,218 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -import { defProp } from 'core/const/props'; - -import { getComponentContext } from 'core/component/context'; -import type { ComponentMeta, ComponentAccessor, ComponentMethod } from 'core/component/interface'; - -const ALREADY_PASSED = Symbol('This target is passed'); - -/** - * Loops through the prototype of the passed component constructor and - * adds methods and accessors to the specified metaobject - * - * @param meta - * @param [constructor] - */ -export function addMethodsToMeta(meta: ComponentMeta, constructor: Function = meta.constructor): void { - // For smart components, this method can be called more than once - if (constructor.hasOwnProperty(ALREADY_PASSED)) { - return; - } - - Object.defineProperty(constructor, ALREADY_PASSED, {value: true}); - - const { - component, - componentName: src, - - props, - fields, - systemFields, - - computedFields, - accessors, - methods, - - metaInitializers - } = meta; - - const - proto = constructor.prototype, - descriptors = Object.getOwnPropertyDescriptors(proto), - descriptorKeys = Object.keys(descriptors); - - for (let i = 0; i < descriptorKeys.length; i++) { - const name = descriptorKeys[i]; - - if (name === 'constructor') { - continue; - } - - const desc = descriptors[name]; - - // Methods - if ('value' in desc) { - const method = desc.value; - - if (!Object.isFunction(method)) { - continue; - } - - const methodDesc: ComponentMethod = Object.assign(methods[name] ?? {watchers: {}, hooks: {}}, {src, fn: method}); - methods[name] = methodDesc; - - component.methods[name] = method; - - // eslint-disable-next-line func-style - const wrapper = function wrapper(this: object) { - // eslint-disable-next-line prefer-rest-params - return method.apply(getComponentContext(this), arguments); - }; - - if (wrapper.length !== method.length) { - Object.defineProperty(wrapper, 'length', {get: () => method.length}); - } - - component.methods[name] = wrapper; - - const - watchers = methodDesc.watchers != null ? Object.keys(methodDesc.watchers) : [], - hooks = methodDesc.hooks != null ? Object.keys(methodDesc.hooks) : []; - - if (watchers.length > 0 || hooks.length > 0) { - metaInitializers.set(name, (meta) => { - const isFunctional = meta.params.functional === true; - - for (const watcherName of watchers) { - const watcher = methodDesc.watchers![watcherName]; - - if (watcher == null || isFunctional && watcher.functional === false) { - continue; - } - - const watcherListeners = meta.watchers[watcherName] ?? []; - meta.watchers[watcherName] = watcherListeners; - - watcherListeners.push({ - ...watcher, - method: name, - args: Array.toArray(watcher.args), - handler: method - }); - } - - for (const hookName of hooks) { - const hook = methodDesc.hooks![hookName]; - - if (isFunctional && hook.functional === false) { - continue; - } - - meta.hooks[hookName].push({...hook, fn: method}); - } - }); - } - - // Accessors - } else { - const - propKey = `${name}Prop`, - storeKey = `${name}Store`; - - let - type: 'accessors' | 'computedFields' = 'accessors', - tiedWith: CanNull = null; - - // Computed fields are cached by default - if ( - name in computedFields || - !(name in accessors) && (tiedWith = props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) - ) { - type = 'computedFields'; - } - - let field: Dictionary; - - if (props[name] != null) { - field = props; - - } else if (fields[name] != null) { - field = fields; - - } else { - field = systemFields; - } - - const store = meta[type]; - - // If we already have a property by this key, like a prop or field, - // we need to delete it to correct override - if (field[name] != null) { - Object.defineProperty(proto, name, defProp); - delete field[name]; - } - - const - old = store[name], - set = desc.set ?? old?.set, - get = desc.get ?? old?.get; - - // To use `super` within the setter, we also create a new method with a name `${key}Setter` - if (set != null) { - const methodName = `${name}Setter`; - proto[methodName] = set; - - meta.methods[methodName] = { - src, - fn: set, - watchers: {}, - hooks: {} - }; - } - - // To using `super` within the getter, we also create a new method with a name `${key}Getter` - if (get != null) { - const methodName = `${name}Getter`; - proto[methodName] = get; - - meta.methods[methodName] = { - src, - fn: get, - watchers: {}, - hooks: {} - }; - } - - const accessor: ComponentAccessor = Object.assign(store[name] ?? {cache: false}, { - src, - get, - set - }); - - store[name] = accessor; - - if (accessor.cache === 'auto') { - component.computed[name] = { - get: accessor.get, - set: accessor.set - }; - } - - // eslint-disable-next-line eqeqeq - if (accessor.functional === undefined && meta.params.functional === null) { - accessor.functional = false; - } - - if (tiedWith != null) { - accessor.tiedWith = tiedWith; - } - } - } -} From 571a66e6ad07a1d5f14fffc1c1dbc322b3f97bbb Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 17:59:48 +0300 Subject: [PATCH 244/334] chore: stylish fixes --- src/core/component/engines/vue3/render.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/component/engines/vue3/render.ts b/src/core/component/engines/vue3/render.ts index b82c2e0616..20da22fca2 100644 --- a/src/core/component/engines/vue3/render.ts +++ b/src/core/component/engines/vue3/render.ts @@ -56,7 +56,8 @@ import { wrapWithDirectives, wrapResolveDirective, - wrapMergeProps, wrapWithCtx + wrapMergeProps, + wrapWithCtx } from 'core/component/render'; From b75754e8c9ffe60e256499a8bc52ad11c849936b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 19:55:50 +0300 Subject: [PATCH 245/334] chore: optimized meta inheritance --- src/core/component/accessor/index.ts | 14 +- .../decorators/computed/decorator.ts | 6 +- src/core/component/decorators/helpers.ts | 9 +- .../component/decorators/hook/decorator.ts | 32 +++- .../component/decorators/hook/interface.ts | 2 +- .../component/decorators/method/decorator.ts | 114 ++++++++----- .../component/decorators/prop/decorator.ts | 29 +++- .../component/decorators/system/decorator.ts | 32 +++- .../component/decorators/watch/decorator.ts | 62 +++++-- src/core/component/meta/field.ts | 9 +- src/core/component/meta/inherit.ts | 151 ++---------------- 11 files changed, 236 insertions(+), 224 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index c5ea698f4c..eb0b7a92f8 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -78,11 +78,8 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const isFunctional = meta.params.functional === true; - const accessorsNames = Object.keys(meta.accessors); - - for (let i = 0; i < accessorsNames.length; i++) { - const name = accessorsNames[i]; - + // eslint-disable-next-line guard-for-in + for (const name in meta.accessors) { const accessor = meta.accessors[name]; const tiedWith = tiedFields[name]; @@ -166,11 +163,8 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const cachedAccessors = new Set(); - const computedNames = Object.keys(meta.computedFields); - - for (let i = 0; i < computedNames.length; i++) { - const name = computedNames[i]; - + // eslint-disable-next-line guard-for-in + for (const name in meta.computedFields) { const computed = meta.computedFields[name]; const tiedWith = tiedFields[name]; diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts index 094b456ba7..a6aa5f9b44 100644 --- a/src/core/component/decorators/computed/decorator.ts +++ b/src/core/component/decorators/computed/decorator.ts @@ -52,7 +52,9 @@ export function computed(params?: DecoratorComputed): PartDecorator { type = 'computedFields'; } - let accessor: ComponentAccessor = meta[type][accessorName] ?? { + const store = meta[type]; + + let accessor: ComponentAccessor = store[accessorName] ?? { src: meta.componentName, cache: false }; @@ -81,7 +83,7 @@ export function computed(params?: DecoratorComputed): PartDecorator { delete meta[type === 'computedFields' ? 'accessors' : 'computedFields'][accessorName]; - meta[type][accessorName] = accessor; + store[accessorName] = accessor; if (params.dependencies != null && params.dependencies.length > 0) { meta.watchDependencies.set(accessorName, params.dependencies); diff --git a/src/core/component/decorators/helpers.ts b/src/core/component/decorators/helpers.ts index 0da9ad09d6..92b0b0ea08 100644 --- a/src/core/component/decorators/helpers.ts +++ b/src/core/component/decorators/helpers.ts @@ -8,8 +8,6 @@ import { initEmitter } from 'core/component/event'; -import { componentDecoratedKeys } from 'core/component/const'; - import type { ComponentMeta } from 'core/component/meta'; import type { @@ -54,12 +52,7 @@ function createComponentDecorator( partDesc: CanUndef, proto: object ): void { - initEmitter.once('bindConstructor', (componentName: string, regEvent: string) => { - const decoratedKeys = componentDecoratedKeys[componentName] ?? new Set(); - componentDecoratedKeys[componentName] = decoratedKeys; - - decoratedKeys.add(partKey); - + initEmitter.once('bindConstructor', (_componentName: string, regEvent: string) => { initEmitter.once(regEvent, (componentDesc: ComponentDescriptor) => { if (decorator.length <= 3) { (decorator)(componentDesc, partKey, proto); diff --git a/src/core/component/decorators/hook/decorator.ts b/src/core/component/decorators/hook/decorator.ts index e23cc52442..cabae8012b 100644 --- a/src/core/component/decorators/hook/decorator.ts +++ b/src/core/component/decorators/hook/decorator.ts @@ -38,11 +38,33 @@ export function hook(hook: DecoratorHook): PartDecorator { return createComponentDecorator3(({meta}, methodName) => { const methodHooks = Array.toArray(hook); - const method: ComponentMethod = meta.methods[methodName] ?? { - src: meta.componentName, - fn: Object.throw, - hooks: {} - }; + let method: ComponentMethod; + + if (meta.methods.hasOwnProperty(methodName)) { + method = meta.methods[methodName]!; + + } else { + const parent = meta.methods[methodName]; + + if (parent != null) { + method = { + ...parent, + src: meta.componentName + }; + + Object.assign(method, parent); + + if (parent.hooks != null) { + method.hooks = Object.create(parent.hooks); + } + + } else { + method = { + src: meta.componentName, + fn: Object.throw + }; + } + } const {hooks = {}} = method; diff --git a/src/core/component/decorators/hook/interface.ts b/src/core/component/decorators/hook/interface.ts index 5f2898c57b..938b220d3d 100644 --- a/src/core/component/decorators/hook/interface.ts +++ b/src/core/component/decorators/hook/interface.ts @@ -7,7 +7,7 @@ */ import type { Hook } from 'core/component/interface'; -import type { DecoratorFunctionalOptions } from 'core/component/decorators/interface/types'; +import type { DecoratorFunctionalOptions } from 'core/component/decorators/interface'; export type DecoratorHook = CanArray | diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index 1ee816eaa6..cb3dd0b3e5 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -73,65 +73,91 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p computedFields, accessors, - methods, metaInitializers } = meta; if (type === 'method') { - const method = proto[name]; + let method: ComponentMethod; - const methodDesc: ComponentMethod = Object.assign(methods[name] ?? {watchers: {}, hooks: {}}, {src, fn: method}); - methods[name] = methodDesc; + const fn = proto[name]; - component.methods[name] = method; + if (meta.methods.hasOwnProperty(name)) { + method = meta.methods[name]!; + method.fn = fn; + + } else { + const parent = meta.methods[name]; + + if (parent != null) { + method = {...parent, src, fn}; + + if (parent.hooks != null) { + fn.hooks = Object.create(parent.hooks); + } + + if (parent.watchers != null) { + fn.watchers = Object.create(parent.watchers); + } + + } else { + method = {src, fn}; + } + } + + meta.methods[name] = method; + component.methods[name] = fn; // eslint-disable-next-line func-style const wrapper = function wrapper(this: object) { // eslint-disable-next-line prefer-rest-params - return method.apply(getComponentContext(this), arguments); + return fn.apply(getComponentContext(this), arguments); }; - if (wrapper.length !== method.length) { - Object.defineProperty(wrapper, 'length', {get: () => method.length}); + if (wrapper.length !== fn.length) { + Object.defineProperty(wrapper, 'length', {get: () => fn.length}); } component.methods[name] = wrapper; - const - watchers = methodDesc.watchers != null ? Object.keys(methodDesc.watchers) : [], - hooks = methodDesc.hooks != null ? Object.keys(methodDesc.hooks) : []; + const {hooks, watchers} = method; - if (watchers.length > 0 || hooks.length > 0) { + if (hooks != null || watchers != null) { metaInitializers.set(name, (meta) => { const isFunctional = meta.params.functional === true; - for (const watcherName of watchers) { - const watcher = methodDesc.watchers![watcherName]; + if (hooks != null) { + // eslint-disable-next-line guard-for-in + for (const hookName in hooks) { + const hook = hooks[hookName]; + + if (isFunctional && hook.functional === false) { + continue; + } - if (watcher == null || isFunctional && watcher.functional === false) { - continue; + meta.hooks[hookName].push({...hook, fn}); } + } - const watcherListeners = meta.watchers[watcherName] ?? []; - meta.watchers[watcherName] = watcherListeners; + if (watchers != null) { + // eslint-disable-next-line guard-for-in + for (const watcherName in watchers) { + const watcher = watchers[watcherName]; - watcherListeners.push({ - ...watcher, - method: name, - args: Array.toArray(watcher.args), - handler: method - }); - } + if (watcher == null || isFunctional && watcher.functional === false) { + continue; + } - for (const hookName of hooks) { - const hook = methodDesc.hooks![hookName]; + const watcherListeners = meta.watchers[watcherName] ?? []; + meta.watchers[watcherName] = watcherListeners; - if (isFunctional && hook.functional === false) { - continue; + watcherListeners.push({ + ...watcher, + method: name, + args: Array.toArray(watcher.args), + handler: fn + }); } - - meta.hooks[hookName].push({...hook, fn: method}); } }); } @@ -182,7 +208,11 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p const old = store[name], + + // eslint-disable-next-line @v4fire/unbound-method set = desc.set ?? old?.set, + + // eslint-disable-next-line @v4fire/unbound-method get = desc.get ?? old?.get; // To use `super` within the setter, we also create a new method with a name `${key}Setter` @@ -211,11 +241,23 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p }; } - const accessor: ComponentAccessor = Object.assign(store[name] ?? {cache: false}, { - src, - get, - set - }); + let accessor: ComponentAccessor; + + if (store.hasOwnProperty(name)) { + accessor = store[name]!; + + } else { + const parent = store[name]; + + accessor = {src, cache: false}; + + if (parent != null) { + Object.assign(accessor, parent); + } + } + + accessor.get = get; + accessor.set = set; store[name] = accessor; diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 2c2c884218..d34969c4dd 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -91,10 +91,31 @@ export function regProp(propName: string, typeOrParams: Nullablewatcher); + let method: ComponentMethod; - const method: ComponentMethod = meta.methods[key] ?? { - src: meta.componentName, - fn: Object.throw, - watchers: {} - }; + if (meta.methods.hasOwnProperty(key)) { + method = meta.methods[key]!; + + } else { + const parent = meta.methods[key]; + + if (parent != null) { + method = { + ...parent, + src: meta.componentName + }; + + if (parent.watchers != null) { + method.watchers = Object.create(parent.watchers); + } + + } else { + method = { + src: meta.componentName, + fn: Object.throw + }; + } + } const {watchers = {}} = method; - for (const methodWatcher of methodWatchers) { + for (const methodWatcher of Array.toArray(watcher)) { if (Object.isString(methodWatcher)) { watchers[methodWatcher] = normalizeFunctionalParams({path: methodWatcher}, meta); @@ -174,10 +192,32 @@ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): store = meta.systemFields; } - const field: ComponentProp | ComponentField = store[key] ?? { - src: meta.componentName, - meta: {} - }; + let field: ComponentProp | ComponentField; + + if (store.hasOwnProperty(key)) { + field = store[key]!; + + } else { + const parent = store[key]; + + if (parent != null) { + field = { + ...parent, + src: meta.componentName, + meta: {...parent.meta} + }; + + if (parent.watchers != null) { + field.watchers = new Map(parent.watchers); + } + + } else { + field = { + src: meta.componentName, + meta: {} + }; + } + } const {watchers = new Map()} = field; diff --git a/src/core/component/meta/field.ts b/src/core/component/meta/field.ts index 3d2621b2a9..031b2e6d23 100644 --- a/src/core/component/meta/field.ts +++ b/src/core/component/meta/field.ts @@ -53,7 +53,14 @@ export function getFieldWeight(field: CanUndef, scope: Dictionar * @param fields */ export function sortFields(fields: Dictionary): ComponentFieldInitializers { - return Object.entries(fields).sort(([aName], [bName]) => { + const list: Array<[string, ComponentField]> = []; + + // eslint-disable-next-line guard-for-in + for (const name in fields) { + list.push([name, fields[name]!]); + } + + return list.sort(([aName], [bName]) => { const aWeight = getFieldWeight(fields[aName], fields), bWeight = getFieldWeight(fields[bName], fields); diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 95242914c6..9f172627fd 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -6,9 +6,9 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -import { componentDecoratedKeys, PARENT } from 'core/component/const'; +import { PARENT } from 'core/component/const'; -import type { ModDeclVal, FieldWatcher } from 'core/component/interface'; +import type { ModDeclVal } from 'core/component/interface'; import type { ComponentMeta } from 'core/component/meta/interface'; /** @@ -19,8 +19,6 @@ import type { ComponentMeta } from 'core/component/meta/interface'; * @param parentMeta */ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): ComponentMeta { - const decoratedKeys = componentDecoratedKeys[meta.componentName]; - meta.tiedFields = {...parentMeta.tiedFields}; if (parentMeta.metaInitializers.size > 0) { @@ -33,21 +31,17 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com inheritParams(meta, parentMeta); - inheritProp(meta.props, parentMeta.props); + meta.props = Object.create(parentMeta.props); + meta.fields = Object.create(parentMeta.fields); + meta.systemFields = Object.create(parentMeta.systemFields); + + meta.accessors = Object.create(parentMeta.accessors); + meta.computedFields = Object.create(parentMeta.computedFields); + meta.methods = Object.create(parentMeta.methods); meta.component.props = {...parentMeta.component.props}; meta.component.attrs = {...parentMeta.component.attrs}; - - inheritField(meta.fields, parentMeta.fields); - inheritField(meta.systemFields, parentMeta.systemFields); - - inheritAccessors(meta.accessors, parentMeta.accessors); - inheritAccessors(meta.computedFields, parentMeta.computedFields); - meta.component.computed = {...parentMeta.component.computed}; - - inheritMethods(meta.methods, parentMeta.methods); - meta.component.methods = {...parentMeta.component.methods}; if (meta.params.partial == null) { @@ -55,133 +49,6 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com } return meta; - - function inheritProp(current: ComponentMeta['props'], parent: ComponentMeta['props']) { - const keys = Object.keys(parent); - - for (let i = 0; i < keys.length; i++) { - const - propName = keys[i], - parentProp = parent[propName]; - - if (parentProp == null) { - continue; - } - - if (decoratedKeys == null || !decoratedKeys.has(propName)) { - current[propName] = parentProp; - continue; - } - - let watchers: CanUndef>; - - if (parentProp.watchers != null) { - for (const watcher of parentProp.watchers.values()) { - watchers ??= new Map(); - watchers.set(watcher.handler, {...watcher}); - } - } - - current[propName] = {...parentProp, watchers}; - } - } - - function inheritField(current: ComponentMeta['fields'], parent: ComponentMeta['fields']) { - const keys = Object.keys(parent); - - for (let i = 0; i < keys.length; i++) { - const - fieldName = keys[i], - parentField = parent[fieldName]; - - if (parentField == null) { - continue; - } - - if (decoratedKeys == null || !decoratedKeys.has(fieldName)) { - current[fieldName] = parentField; - continue; - } - - let - after: CanUndef>, - watchers: CanUndef>; - - if (parentField.watchers != null) { - for (const watcher of parentField.watchers.values()) { - watchers ??= new Map(); - watchers.set(watcher.handler, {...watcher}); - } - } - - if (parentField.after != null) { - for (const name of parentField.after) { - after ??= new Set(); - after.add(name); - } - } - - current[fieldName] = {...parentField, after, watchers}; - } - } - - function inheritAccessors(current: ComponentMeta['accessors'], parent: ComponentMeta['accessors']) { - const keys = Object.keys(parent); - - for (let i = 0; i < keys.length; i++) { - const accessorName = keys[i]; - current[accessorName] = {...parent[accessorName]!}; - } - } - - function inheritMethods(current: ComponentMeta['methods'], parent: ComponentMeta['methods']) { - const keys = Object.keys(parent); - - for (let i = 0; i < keys.length; i++) { - const - methodName = keys[i], - parentMethod = parent[methodName]; - - if (parentMethod == null) { - continue; - } - - if (decoratedKeys == null || !decoratedKeys.has(methodName)) { - current[methodName] = {...parentMethod}; - continue; - } - - const - watchers = {}, - hooks = {}; - - if (parentMethod.watchers != null) { - const keys = Object.keys(parentMethod.watchers); - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - watchers[key] = {...parentMethod.watchers[key]}; - } - } - - if (parentMethod.hooks != null) { - const keys = Object.keys(parentMethod.hooks); - - for (let i = 0; i < keys.length; i++) { - const - key = keys[i], - hook = parentMethod.hooks[key]; - - hooks[key] = { - ...hook, - after: Object.size(hook.after) > 0 ? new Set(hook.after) : undefined - }; - } - } - - current[methodName] = {...parentMethod, watchers, hooks}; - } - } } /** From 1be88537d2d2d036cc5f9778f191a5307f3367ca Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 20:14:19 +0300 Subject: [PATCH 246/334] chore: review --- .../component/decorators/method/decorator.ts | 2 - src/core/component/decorators/method/index.ts | 1 + .../component/decorators/system/decorator.ts | 45 +++++++++++-------- .../component/decorators/system/interface.ts | 2 + 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index cb3dd0b3e5..66ccd84b82 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -18,8 +18,6 @@ import type { ComponentAccessor, ComponentMethod } from 'core/component/interfac import type { PartDecorator } from 'core/component/decorators/interface'; import type { MethodType } from 'core/component/decorators/method/interface'; -export * from 'core/component/decorators/method/interface'; - /** * Marks a class method or accessor as a component part. * diff --git a/src/core/component/decorators/method/index.ts b/src/core/component/decorators/method/index.ts index 56719ae095..8f76300c5d 100644 --- a/src/core/component/decorators/method/index.ts +++ b/src/core/component/decorators/method/index.ts @@ -12,3 +12,4 @@ */ export * from 'core/component/decorators/method/decorator'; +export * from 'core/component/decorators/method/interface'; diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index e7cb8ad4ea..32a7a9bd48 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -15,7 +15,16 @@ import type { ComponentField } from 'core/component/interface'; import type { ComponentMeta } from 'core/component/meta'; import type { PartDecorator } from 'core/component/decorators/interface'; -import type { InitFieldFn, DecoratorSystem, DecoratorField } from 'core/component/decorators/system/interface'; + +import type { + + FieldCluster, + InitFieldFn, + + DecoratorSystem, + DecoratorField + +} from 'core/component/decorators/system/interface'; const INIT = Symbol('The field initializer'); @@ -26,7 +35,7 @@ const INIT = Symbol('The field initializer'); * @decorator * * @param [initOrParams] - a function to initialize the field value or an object with field parameters - * @param [type] - the type of the registered field: `systemFields` or `fields` + * @param [cluster] - the cluster for the registered field: `systemFields` or `fields` * * @example * ```typescript @@ -42,22 +51,22 @@ const INIT = Symbol('The field initializer'); * } * ``` */ -export function system(initOrParams?: InitFieldFn | DecoratorSystem, type?: 'systemFields'): PartDecorator; +export function system(initOrParams?: InitFieldFn | DecoratorSystem, cluster?: 'systemFields'): PartDecorator; /** * Marks a class property as a field. * * @param [initOrParams] - a function to initialize the field value or an object with field parameters - * @param [type] - the type of the registered field: `systemFields` or `fields` + * @param [cluster] - the cluster for the registered field: `systemFields` or `fields` */ -export function system(initOrParams: CanUndef, type: 'fields'): PartDecorator; +export function system(initOrParams: CanUndef, cluster: 'fields'): PartDecorator; export function system( initOrParams?: InitFieldFn | DecoratorSystem | DecoratorField, - type: 'fields' | 'systemFields' = 'systemFields' + cluster: FieldCluster = 'systemFields' ): PartDecorator { return createComponentDecorator3((desc, fieldName) => { - regField(fieldName, type, initOrParams, desc.meta); + regField(fieldName, cluster, initOrParams, desc.meta); }); } @@ -65,13 +74,13 @@ export function system( * Registers a component field in the specified metaobject * * @param fieldName - the name of the field - * @param type - the type of the registered field: `systemFields` or `fields` + * @param cluster - the cluster for the registered field: `systemFields` or `fields` * @param initOrParams - a function to initialize the field value or an object with field parameters * @param meta - the metaobject where the field is registered */ export function regField( fieldName: string, - type: 'systemFields' | 'fields', + cluster: FieldCluster, initOrParams: Nullable, meta: ComponentMeta ): void { @@ -88,13 +97,15 @@ export function regField( delete accessors[fieldName]; } + const store = meta[cluster]; + // Handling the situation when a field changes type during inheritance, // for example, it was a @prop in the parent component and became a @system - for (const anotherType of ['props', type === 'fields' ? 'systemFields' : 'fields']) { - const cluster = meta[anotherType]; + for (const anotherType of ['props', cluster === 'fields' ? 'systemFields' : 'fields']) { + const anotherStore = meta[anotherType]; - if (fieldName in cluster) { - const field: ComponentField = {...cluster[fieldName]}; + if (fieldName in anotherStore) { + const field: ComponentField = {...anotherStore[fieldName]}; // Do not inherit the `functional` option in this case delete field.functional; @@ -108,15 +119,13 @@ export function regField( } } - meta[type][fieldName] = field; - delete cluster[fieldName]; + store[fieldName] = field; + delete anotherStore[fieldName]; break; } } - const store = meta[type]; - let field: ComponentField; if (store.hasOwnProperty(fieldName)) { @@ -178,7 +187,7 @@ export function regField( } }, meta); - meta[type][fieldName] = field; + store[fieldName] = field; if (field.init == null || !(INIT in field.init)) { const defValue = field.default; diff --git a/src/core/component/decorators/system/interface.ts b/src/core/component/decorators/system/interface.ts index a3465dbb53..63a324ac5e 100644 --- a/src/core/component/decorators/system/interface.ts +++ b/src/core/component/decorators/system/interface.ts @@ -10,6 +10,8 @@ import type { ComponentInterface } from 'core/component/interface'; import type { DecoratorFieldWatcher } from 'core/component/decorators/watch'; import type { DecoratorFunctionalOptions } from 'core/component/decorators/interface'; +export type FieldCluster = 'fields' | 'systemFields'; + export interface DecoratorSystem< Ctx extends ComponentInterface = ComponentInterface, A = unknown, From 5be5ddd8192bdd1a655fd10e34fbbe0e7a622f40 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 17 Oct 2024 23:59:31 +0300 Subject: [PATCH 247/334] feat: added support for `this` within default values --- build/ts-transformers/resister-component-parts/index.js | 5 +++-- src/core/component/decorators/system/decorator.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build/ts-transformers/resister-component-parts/index.js b/build/ts-transformers/resister-component-parts/index.js index 7b3f5e3628..556ea25c90 100644 --- a/build/ts-transformers/resister-component-parts/index.js +++ b/build/ts-transformers/resister-component-parts/index.js @@ -156,11 +156,12 @@ function addDefaultValueDecorator(context, node) { true ); - getter = factory.createArrowFunction( + getter = factory.createFunctionExpression( undefined, undefined, - [], + 'getter', undefined, + [], undefined, getterValue ); diff --git a/src/core/component/decorators/system/decorator.ts b/src/core/component/decorators/system/decorator.ts index 32a7a9bd48..48de69b33a 100644 --- a/src/core/component/decorators/system/decorator.ts +++ b/src/core/component/decorators/system/decorator.ts @@ -196,7 +196,7 @@ export function regField( const customInit = field.init; field.init = (ctx, store) => { - const val = customInit(ctx, store); + const val = customInit.call(ctx, ctx, store); if (val === undefined && defValue !== undefined) { if (store[fieldName] === undefined) { From 9ae28af66bb13ce606ed917b85a9dfc88d763775 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 18 Oct 2024 00:26:55 +0300 Subject: [PATCH 248/334] chore: optimization --- .../decorators/computed/decorator.ts | 12 +- .../component/decorators/hook/decorator.ts | 11 +- .../component/decorators/prop/decorator.ts | 91 +++++++------ .../component/decorators/system/decorator.ts | 124 ++++++++++-------- .../component/decorators/watch/decorator.ts | 22 +++- 5 files changed, 158 insertions(+), 102 deletions(-) diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts index a6aa5f9b44..0b5412d1e5 100644 --- a/src/core/component/decorators/computed/decorator.ts +++ b/src/core/component/decorators/computed/decorator.ts @@ -41,7 +41,7 @@ export function computed(params?: DecoratorComputed): PartDecorator { delete meta.fields[accessorName]; delete meta.systemFields[accessorName]; - let type: 'accessors' | 'computedFields' = 'accessors'; + let cluster: 'accessors' | 'computedFields' = 'accessors'; if ( params.cache === true || @@ -49,17 +49,17 @@ export function computed(params?: DecoratorComputed): PartDecorator { params.cache === 'forever' || params.cache !== false && (Object.isArray(params.dependencies) || accessorName in meta.computedFields) ) { - type = 'computedFields'; + cluster = 'computedFields'; } - const store = meta[type]; + const store = meta[cluster]; let accessor: ComponentAccessor = store[accessorName] ?? { src: meta.componentName, cache: false }; - const needOverrideComputed = type === 'accessors' ? + const needOverrideComputed = cluster === 'accessors' ? accessorName in meta.computedFields : !('cache' in params) && accessorName in meta.accessors; @@ -77,11 +77,11 @@ export function computed(params?: DecoratorComputed): PartDecorator { accessor = normalizeFunctionalParams({ ...accessor, ...params, - cache: type === 'computedFields' ? params.cache ?? true : false + cache: cluster === 'computedFields' ? params.cache ?? true : false }, meta); } - delete meta[type === 'computedFields' ? 'accessors' : 'computedFields'][accessorName]; + delete meta[cluster === 'computedFields' ? 'accessors' : 'computedFields'][accessorName]; store[accessorName] = accessor; diff --git a/src/core/component/decorators/hook/decorator.ts b/src/core/component/decorators/hook/decorator.ts index cabae8012b..ef7deb93d4 100644 --- a/src/core/component/decorators/hook/decorator.ts +++ b/src/core/component/decorators/hook/decorator.ts @@ -40,7 +40,9 @@ export function hook(hook: DecoratorHook): PartDecorator { let method: ComponentMethod; - if (meta.methods.hasOwnProperty(methodName)) { + const alreadyDefined = meta.methods.hasOwnProperty(methodName); + + if (alreadyDefined) { method = meta.methods[methodName]!; } else { @@ -86,6 +88,11 @@ export function hook(hook: DecoratorHook): PartDecorator { } } - meta.methods[methodName] = normalizeFunctionalParams({...method, hooks}, meta); + if (alreadyDefined) { + method.hooks = hooks; + + } else { + meta.methods[methodName] = normalizeFunctionalParams({...method, hooks}, meta); + } }); } diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index d34969c4dd..6a3f3a1018 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -58,45 +58,47 @@ export function regProp(propName: string, typeOrParams: Nullable 0) { diff --git a/src/core/component/decorators/watch/decorator.ts b/src/core/component/decorators/watch/decorator.ts index 430842182b..d90a05d647 100644 --- a/src/core/component/decorators/watch/decorator.ts +++ b/src/core/component/decorators/watch/decorator.ts @@ -139,7 +139,9 @@ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): function decorateMethod() { let method: ComponentMethod; - if (meta.methods.hasOwnProperty(key)) { + const alreadyDefined = meta.methods.hasOwnProperty(key); + + if (alreadyDefined) { method = meta.methods[key]!; } else { @@ -174,7 +176,12 @@ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): } } - meta.methods[key] = normalizeFunctionalParams({...method, watchers}, meta); + if (alreadyDefined) { + method.watchers = watchers; + + } else { + meta.methods[key] = normalizeFunctionalParams({...method, watchers}, meta); + } } function decorateField() { @@ -194,7 +201,9 @@ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): let field: ComponentProp | ComponentField; - if (store.hasOwnProperty(key)) { + const alreadyDefined = store.hasOwnProperty(key); + + if (alreadyDefined) { field = store[key]!; } else { @@ -230,7 +239,12 @@ export function watch(watcher: DecoratorFieldWatcher | DecoratorMethodWatcher): } } - store[key] = normalizeFunctionalParams({...field, watchers}, meta); + if (alreadyDefined) { + field.watchers = watchers; + + } else { + store[key] = normalizeFunctionalParams({...field, watchers}, meta); + } } }); } From 9e5263ceaba0c4250110978aa6bc209a6208bdd1 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 18 Oct 2024 01:19:40 +0300 Subject: [PATCH 249/334] fix: reg methods after properties --- src/components/traits/index.ts | 2 +- src/core/component/decorators/helpers.ts | 17 +++++++++++------ .../component/decorators/method/decorator.ts | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/traits/index.ts b/src/components/traits/index.ts index b18b76d1c3..a5910cf691 100644 --- a/src/components/traits/index.ts +++ b/src/components/traits/index.ts @@ -67,7 +67,7 @@ import { regMethod, MethodType } from 'core/component/decorators/method'; */ export function derive(...traits: Function[]) { return (target: Function): void => { - initEmitter.once('bindConstructor', (_: string, regEvent: string) => { + initEmitter.once('after:bindConstructor', (_: string, regEvent: string) => { initEmitter.once(regEvent, ({meta}: ComponentDescriptor) => { const proto = target.prototype; diff --git a/src/core/component/decorators/helpers.ts b/src/core/component/decorators/helpers.ts index 92b0b0ea08..19f6af71b1 100644 --- a/src/core/component/decorators/helpers.ts +++ b/src/core/component/decorators/helpers.ts @@ -27,10 +27,11 @@ import type { * The decorator function expects three input arguments (excluding the object descriptor). * * @param decorator + * @param [append] - if true, the decorator handler will be added to the end of the queue */ -export function createComponentDecorator3(decorator: ComponentPartDecorator3): PartDecorator { +export function createComponentDecorator3(decorator: ComponentPartDecorator3, append?: boolean): PartDecorator { return (proto: object, partKey: string) => { - createComponentDecorator(decorator, partKey, undefined, proto); + createComponentDecorator(decorator, partKey, undefined, proto, append); }; } @@ -39,10 +40,11 @@ export function createComponentDecorator3(decorator: ComponentPartDecorator3): P * The decorator function expects four input arguments (including the object descriptor). * * @param decorator + * @param [append] - if true, the decorator handler will be added to the end of the queue */ -export function createComponentDecorator4(decorator: ComponentPartDecorator4): PartDecorator { +export function createComponentDecorator4(decorator: ComponentPartDecorator4, append?: boolean): PartDecorator { return (proto: object, partKey: string, partDesc?: PropertyDescriptor) => { - createComponentDecorator(decorator, partKey, partDesc, proto); + createComponentDecorator(decorator, partKey, partDesc, proto, append); }; } @@ -50,9 +52,12 @@ function createComponentDecorator( decorator: ComponentPartDecorator3 | ComponentPartDecorator4, partKey: string, partDesc: CanUndef, - proto: object + proto: object, + append?: boolean ): void { - initEmitter.once('bindConstructor', (_componentName: string, regEvent: string) => { + const event = append ? 'after:bindConstructor' : 'bindConstructor'; + + initEmitter.once(event, (_componentName: string, regEvent: string) => { initEmitter.once(regEvent, (componentDesc: ComponentDescriptor) => { if (decorator.length <= 3) { (decorator)(componentDesc, partKey, proto); diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index 66ccd84b82..0384552cde 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -49,7 +49,7 @@ import type { MethodType } from 'core/component/decorators/method/interface'; export function method(type: MethodType): PartDecorator { return createComponentDecorator3((desc, name, proto) => { regMethod(name, type, desc.meta, proto); - }); + }, true); } /** From bfcb97195caee1cc7db9da81740f6e68945668b4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 18 Oct 2024 01:57:10 +0300 Subject: [PATCH 250/334] fix: fix after refactoring --- src/core/component/decorators/component/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 31e5d5c056..7dd2c92d54 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -92,6 +92,7 @@ export function component(opts?: ComponentOptions): Function { const regEvent = `constructor.${componentNormalizedName}.${componentInfo.layer}`; initEmitter.emit('bindConstructor', componentNormalizedName, regEvent); + initEmitter.emit('after:bindConstructor', componentNormalizedName, regEvent); if (isPartial) { pushToInitList(() => { From 62d56bfdf8ad0652d90822bfe5fb75c509d55fdc Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 18 Oct 2024 01:59:41 +0300 Subject: [PATCH 251/334] fix: reverted after:bindConstructor --- src/components/traits/index.ts | 2 +- .../component/decorators/component/index.ts | 1 - src/core/component/decorators/helpers.ts | 17 ++++++----------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/components/traits/index.ts b/src/components/traits/index.ts index a5910cf691..b18b76d1c3 100644 --- a/src/components/traits/index.ts +++ b/src/components/traits/index.ts @@ -67,7 +67,7 @@ import { regMethod, MethodType } from 'core/component/decorators/method'; */ export function derive(...traits: Function[]) { return (target: Function): void => { - initEmitter.once('after:bindConstructor', (_: string, regEvent: string) => { + initEmitter.once('bindConstructor', (_: string, regEvent: string) => { initEmitter.once(regEvent, ({meta}: ComponentDescriptor) => { const proto = target.prototype; diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 7dd2c92d54..31e5d5c056 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -92,7 +92,6 @@ export function component(opts?: ComponentOptions): Function { const regEvent = `constructor.${componentNormalizedName}.${componentInfo.layer}`; initEmitter.emit('bindConstructor', componentNormalizedName, regEvent); - initEmitter.emit('after:bindConstructor', componentNormalizedName, regEvent); if (isPartial) { pushToInitList(() => { diff --git a/src/core/component/decorators/helpers.ts b/src/core/component/decorators/helpers.ts index 19f6af71b1..92b0b0ea08 100644 --- a/src/core/component/decorators/helpers.ts +++ b/src/core/component/decorators/helpers.ts @@ -27,11 +27,10 @@ import type { * The decorator function expects three input arguments (excluding the object descriptor). * * @param decorator - * @param [append] - if true, the decorator handler will be added to the end of the queue */ -export function createComponentDecorator3(decorator: ComponentPartDecorator3, append?: boolean): PartDecorator { +export function createComponentDecorator3(decorator: ComponentPartDecorator3): PartDecorator { return (proto: object, partKey: string) => { - createComponentDecorator(decorator, partKey, undefined, proto, append); + createComponentDecorator(decorator, partKey, undefined, proto); }; } @@ -40,11 +39,10 @@ export function createComponentDecorator3(decorator: ComponentPartDecorator3, ap * The decorator function expects four input arguments (including the object descriptor). * * @param decorator - * @param [append] - if true, the decorator handler will be added to the end of the queue */ -export function createComponentDecorator4(decorator: ComponentPartDecorator4, append?: boolean): PartDecorator { +export function createComponentDecorator4(decorator: ComponentPartDecorator4): PartDecorator { return (proto: object, partKey: string, partDesc?: PropertyDescriptor) => { - createComponentDecorator(decorator, partKey, partDesc, proto, append); + createComponentDecorator(decorator, partKey, partDesc, proto); }; } @@ -52,12 +50,9 @@ function createComponentDecorator( decorator: ComponentPartDecorator3 | ComponentPartDecorator4, partKey: string, partDesc: CanUndef, - proto: object, - append?: boolean + proto: object ): void { - const event = append ? 'after:bindConstructor' : 'bindConstructor'; - - initEmitter.once(event, (_componentName: string, regEvent: string) => { + initEmitter.once('bindConstructor', (_componentName: string, regEvent: string) => { initEmitter.once(regEvent, (componentDesc: ComponentDescriptor) => { if (decorator.length <= 3) { (decorator)(componentDesc, partKey, proto); From 15d7293bf8ba2d1a73994d07970243fcaf06f11f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 18 Oct 2024 02:02:19 +0300 Subject: [PATCH 252/334] chore: stylish fixes --- .../component/decorators/method/decorator.ts | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index 0384552cde..172ed17817 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -49,7 +49,7 @@ import type { MethodType } from 'core/component/decorators/method/interface'; export function method(type: MethodType): PartDecorator { return createComponentDecorator3((desc, name, proto) => { regMethod(name, type, desc.meta, proto); - }, true); + }); } /** @@ -69,10 +69,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p fields, systemFields, - computedFields, - accessors, - - metaInitializers + methods } = meta; if (type === 'method') { @@ -80,12 +77,12 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p const fn = proto[name]; - if (meta.methods.hasOwnProperty(name)) { - method = meta.methods[name]!; + if (methods.hasOwnProperty(name)) { + method = methods[name]!; method.fn = fn; } else { - const parent = meta.methods[name]; + const parent = methods[name]; if (parent != null) { method = {...parent, src, fn}; @@ -103,7 +100,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p } } - meta.methods[name] = method; + methods[name] = method; component.methods[name] = fn; // eslint-disable-next-line func-style @@ -121,7 +118,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p const {hooks, watchers} = method; if (hooks != null || watchers != null) { - metaInitializers.set(name, (meta) => { + meta.metaInitializers.set(name, (meta) => { const isFunctional = meta.params.functional === true; if (hooks != null) { @@ -177,8 +174,8 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p // Computed fields are cached by default if ( - name in computedFields || - !(name in accessors) && (tiedWith = props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) + name in meta.computedFields || + !(name in meta.accessors) && (tiedWith = props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) ) { type = 'computedFields'; } @@ -218,7 +215,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p const methodName = `${name}Setter`; proto[methodName] = set; - meta.methods[methodName] = { + methods[methodName] = { src, fn: set, watchers: {}, @@ -231,7 +228,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p const methodName = `${name}Getter`; proto[methodName] = get; - meta.methods[methodName] = { + methods[methodName] = { src, fn: get, watchers: {}, From 246d4a8e4a2b2329d27bdee277d700d7738f7846 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sun, 20 Oct 2024 16:33:31 +0300 Subject: [PATCH 253/334] chore: stylish fixes --- src/components/directives/bind-with/README.md | 2 +- src/components/directives/image/README.md | 2 +- .../directives/async-target/index.ts | 3 +-- src/core/component/directives/attrs/README.md | 2 +- src/core/component/directives/attrs/index.ts | 26 +++++++------------ src/core/component/directives/helpers.ts | 3 +-- src/core/component/directives/hook/README.md | 2 +- src/core/component/directives/ref/README.md | 2 +- src/core/component/directives/ref/index.ts | 14 +++------- .../component/directives/render/README.md | 2 +- src/core/component/directives/render/index.ts | 9 +++---- src/core/component/directives/tag/README.md | 2 +- 12 files changed, 25 insertions(+), 44 deletions(-) diff --git a/src/components/directives/bind-with/README.md b/src/components/directives/bind-with/README.md index 0f2ec16f50..a17d86c000 100644 --- a/src/components/directives/bind-with/README.md +++ b/src/components/directives/bind-with/README.md @@ -17,7 +17,7 @@ which receives a reference to the element the directive is applied to as its fir } . ``` -## Why is This Directive Needed? +## Why is This Directive Necessary? When using regular components, we don't have to think about how the data used in the template is tied to the component's properties. diff --git a/src/components/directives/image/README.md b/src/components/directives/image/README.md index 07b5f6ea29..0be947654c 100644 --- a/src/components/directives/image/README.md +++ b/src/components/directives/image/README.md @@ -11,7 +11,7 @@ The directive cannot be applied to `img`, `picture`, or `object` tags. } . ``` -## Why is This Directive Needed? +## Why is This Directive Necessary? When working with images, it is common to display a placeholder or an error message during the loading process. However, there is no native API to implement this feature. diff --git a/src/core/component/directives/async-target/index.ts b/src/core/component/directives/async-target/index.ts index 1fe29f3ce5..bd8bc7a9c1 100644 --- a/src/core/component/directives/async-target/index.ts +++ b/src/core/component/directives/async-target/index.ts @@ -20,8 +20,7 @@ export * from 'core/component/directives/async-target/interface'; ComponentEngine.directive('async-target', { beforeCreate(params: DirectiveParams, vnode: VNode): void { - const - ctx = getDirectiveContext(params, vnode); + const ctx = getDirectiveContext(params, vnode); if (ctx == null || params.value === false) { return; diff --git a/src/core/component/directives/attrs/README.md b/src/core/component/directives/attrs/README.md index 60d26bf98c..9ebbfd5c8a 100644 --- a/src/core/component/directives/attrs/README.md +++ b/src/core/component/directives/attrs/README.md @@ -7,7 +7,7 @@ the provided dictionary. < .example v-attrs = {'@click': console.log, class: classes, 'v-show': condition} ``` -## Why is This Directive Needed? +## Why is This Directive Necessary? Often, there are situations where we need to dynamically apply a set of parameters to an element or component. While we have extended versions of the `v-on` and `v-bind` directives for events and attributes, diff --git a/src/core/component/directives/attrs/index.ts b/src/core/component/directives/attrs/index.ts index e4299adf07..ee6d45a5ee 100644 --- a/src/core/component/directives/attrs/index.ts +++ b/src/core/component/directives/attrs/index.ts @@ -115,21 +115,17 @@ ComponentEngine.directive('attrs', { } function parseDirective(attrName: string, attrVal: unknown) { - const - decl = directiveRgxp.exec(attrName); + const decl = directiveRgxp.exec(attrName); - let - value = attrVal; + let value = attrVal; if (decl == null) { throw new SyntaxError('Invalid directive declaration'); } - const - [, name, arg = '', rawModifiers = ''] = decl; + const [, name, arg = '', rawModifiers = ''] = decl; - let - dir: CanUndef; + let dir: CanUndef; switch (name) { case 'show': { @@ -167,8 +163,7 @@ ComponentEngine.directive('attrs', { handlerCache = getHandlerStore(), handlerKey = `onUpdate:${modelProp}:${modelValLink}`; - let - handler = handlerCache.get(handlerKey); + let handler = handlerCache.get(handlerKey); if (handler == null) { handler = (newVal: unknown) => { @@ -316,7 +311,7 @@ ComponentEngine.directive('attrs', { // For the transmission of accessors, `forceUpdate: false` props use events. // For example, `@:value = createPropAccessors(() => someValue)`. // A distinctive feature of such events is the prefix `@:` or `on:`. - // Such events are processed in a special way. + // Such events are processed specially. const isSystemGetter = isPropGetter.test(event); props[event] = attrVal; @@ -380,18 +375,15 @@ ComponentEngine.directive('attrs', { return props; function parseDirective(attrName: string, attrVal: unknown) { - const - decl = directiveRgxp.exec(attrName); + const decl = directiveRgxp.exec(attrName); if (decl == null) { throw new SyntaxError('Invalid directive declaration'); } - const - [, name, arg = '', rawModifiers = ''] = decl; + const [, name, arg = '', rawModifiers = ''] = decl; - let - dir: CanUndef; + let dir: CanUndef; switch (name) { case 'show': { diff --git a/src/core/component/directives/helpers.ts b/src/core/component/directives/helpers.ts index e21c500070..e15615d7ef 100644 --- a/src/core/component/directives/helpers.ts +++ b/src/core/component/directives/helpers.ts @@ -18,8 +18,7 @@ import type { ComponentInterface } from 'core/component/interface'; * @param idsCache - the store for the registered elements */ export function getElementId(el: Element, idsCache: WeakMap): string { - let - id = idsCache.get(el); + let id = idsCache.get(el); if (id == null) { id = Object.fastHash(Math.random()); diff --git a/src/core/component/directives/hook/README.md b/src/core/component/directives/hook/README.md index 8df9285e38..8b37641467 100644 --- a/src/core/component/directives/hook/README.md +++ b/src/core/component/directives/hook/README.md @@ -16,7 +16,7 @@ from the component. } . ``` -## Why is This Directive Needed? +## Why is This Directive Necessary? This directive is typically used with functional components as they do not initially possess their own lifecycle API and can easily be supplemented with it using this directive. diff --git a/src/core/component/directives/ref/README.md b/src/core/component/directives/ref/README.md index ae2882a080..414e0f43da 100644 --- a/src/core/component/directives/ref/README.md +++ b/src/core/component/directives/ref/README.md @@ -13,7 +13,7 @@ This directive is used in conjunction with the standard `ref` directive. < b-button :ref = $resolveRef('button') | v-ref = 'button' ``` -## Why is This Directive Needed? +## Why is This Directive Necessary? V4Fire supports two types of components: regular and functional. From the perspective of the rendering library used, functional components are regular functions that return VNodes. diff --git a/src/core/component/directives/ref/index.ts b/src/core/component/directives/ref/index.ts index e51db49b8c..127c9cbaa2 100644 --- a/src/core/component/directives/ref/index.ts +++ b/src/core/component/directives/ref/index.ts @@ -34,10 +34,7 @@ ComponentEngine.directive('ref', { }); function updateRef(el: Element | ComponentElement, opts: DirectiveOptions, vnode: VNode): void { - const { - value, - instance - } = opts; + const {value, instance} = opts; let ctx = getDirectiveContext(opts, vnode); ctx = Object.cast(ctx?.meta.params.functional === true ? ctx : instance); @@ -65,8 +62,7 @@ function updateRef(el: Element | ComponentElement, opts: DirectiveOptions, vnode refs = ctx.$refs; if (vnode.virtualComponent != null) { - const - refVal = getRefVal(); + const refVal = getRefVal(); if (Object.isArray(refVal)) { refVal[REF_ID] ??= Math.random(); @@ -107,11 +103,9 @@ function updateRef(el: Element | ComponentElement, opts: DirectiveOptions, vnode } function resolveRefVal(key?: PropertyKey) { - const - refVal = getRefVal(); + const refVal = getRefVal(); - let - ref: unknown; + let ref: unknown; if (Object.isArray(refVal)) { if (key != null) { diff --git a/src/core/component/directives/render/README.md b/src/core/component/directives/render/README.md index 1f053c9426..3a0e3b36a0 100644 --- a/src/core/component/directives/render/README.md +++ b/src/core/component/directives/render/README.md @@ -7,7 +7,7 @@ The directive supports several modes of operation: 2. The new VNodes are inserted as child content of the node where the directive is applied. 3. The new VNodes are inserted as a component slot (if the directive is applied to a component). -## Why is This Directive Needed? +## Why is This Directive Necessary? To decompose the template of one component into multiple render functions and utilize their composition. This approach is extremely useful when we have a large template that cannot be divided into independent components. diff --git a/src/core/component/directives/render/index.ts b/src/core/component/directives/render/index.ts index 5ab9106407..eaf67c1c99 100644 --- a/src/core/component/directives/render/index.ts +++ b/src/core/component/directives/render/index.ts @@ -21,8 +21,7 @@ export * from 'core/component/directives/render/interface'; ComponentEngine.directive('render', { beforeCreate(params: DirectiveParams, vnode: VNode): CanUndef { - const - ctx = getDirectiveContext(params, vnode); + const ctx = getDirectiveContext(params, vnode); const newVNode = params.value, @@ -111,8 +110,7 @@ ComponentEngine.directive('render', { return; } - const - {r} = ctx.$renderEngine; + const {r} = ctx.$renderEngine; return r.createVNode.call(ctx, 'ssr-fragment', { innerHTML: getSSRInnerHTML(content) @@ -125,8 +123,7 @@ ComponentEngine.directive('render', { function getDefaultSlotFromChildren(slotName: string): unknown { if (Object.isPlainObject(originalChildren)) { - const - slot = originalChildren[slotName]; + const slot = originalChildren[slotName]; if (Object.isFunction(slot)) { return slot(); diff --git a/src/core/component/directives/tag/README.md b/src/core/component/directives/tag/README.md index 96970d6d49..0e7dae948b 100644 --- a/src/core/component/directives/tag/README.md +++ b/src/core/component/directives/tag/README.md @@ -7,7 +7,7 @@ for dynamically specifying the name of the element tag to which the directive is < div v-tag = 'span' ``` -## Why is This Directive Needed? +## Why is This Directive Necessary? Unlike the component `:is directive`, which can be used for both creating components and regular elements, this directive can only be applied to regular elements, and the passed name is always treated as a regular name, From d4c009c8b35fbd574f848e86669b3c233bc75ff2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sun, 20 Oct 2024 16:47:46 +0300 Subject: [PATCH 254/334] chore: stylish fixes --- src/core/component/engines/vue3/lib.ts | 3 +-- src/core/component/event/emitter.ts | 2 +- src/core/component/reflect/property.ts | 10 +++++----- src/core/component/render/daemon/index.ts | 3 +-- src/core/component/watch/create.ts | 12 ++++-------- src/core/component/watch/helpers.ts | 3 +-- 6 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/core/component/engines/vue3/lib.ts b/src/core/component/engines/vue3/lib.ts index e5a0cffcd4..b948658939 100644 --- a/src/core/component/engines/vue3/lib.ts +++ b/src/core/component/engines/vue3/lib.ts @@ -13,8 +13,7 @@ import { makeLazy } from 'core/lazy'; import { createApp, createSSRApp, defineAsyncComponent, App, Component } from 'vue'; import type { CreateAppFunction } from 'core/component/engines/interface'; -let - ssrContext = SSR || HYDRATION; +let ssrContext = SSR || HYDRATION; const NewApp = function App(component: Component & {el?: Element}, rootProps: Nullable) { const app = Object.create((ssrContext ? createSSRApp : createApp)(component, rootProps)); diff --git a/src/core/component/event/emitter.ts b/src/core/component/event/emitter.ts index d39a7be133..7de1e6162f 100644 --- a/src/core/component/event/emitter.ts +++ b/src/core/component/event/emitter.ts @@ -23,7 +23,7 @@ const originalEmit = globalEmitter.emit.bind(globalEmitter); globalEmitter.emit = (event: string, ...args) => { const res = originalEmit(event, ...args); - log(`global:event:${event.replace(/\./g, ':')}`, ...args); + log(`global:event:${event.replaceAll('.', ':')}`, ...args); return res; }; diff --git a/src/core/component/reflect/property.ts b/src/core/component/reflect/property.ts index da62d366cc..4b15e128aa 100644 --- a/src/core/component/reflect/property.ts +++ b/src/core/component/reflect/property.ts @@ -66,9 +66,11 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr let obj: Nullable = component; - chunks.some((chunk, i, chunks) => { + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + if (obj == null) { - return true; + break; } if (Object.isMap(obj)) { @@ -82,9 +84,7 @@ export function getPropertyInfo(path: string, component: ComponentInterface): Pr component = obj; rootI = i === chunks.length - 1 ? i : i + 1; } - - return false; - }); + } path = chunks.slice(rootI).join('.'); topPath = chunks.slice(0, rootI + 1).join('.'); diff --git a/src/core/component/render/daemon/index.ts b/src/core/component/render/daemon/index.ts index c1301f892f..78669ff8fb 100644 --- a/src/core/component/render/daemon/index.ts +++ b/src/core/component/render/daemon/index.ts @@ -74,8 +74,7 @@ function run(): void { done = opts.weightPerTick; } - const - w = val.weight ?? 1; + const w = val.weight ?? 1; if (done - w < 0 && done !== opts.weightPerTick) { continue; diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 258402b8c0..129624805e 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -258,8 +258,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface return null; } - let - proxy = watchInfo?.value; + let proxy = watchInfo?.value; if (proxy != null) { if (watchInfo == null) { @@ -269,8 +268,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface switch (info.type) { case 'field': case 'system': { - const - propCtx = info.ctx.unsafe; + const propCtx = info.ctx.unsafe; if (!Object.getOwnPropertyDescriptor(propCtx, info.name)?.get) { proxy[watcherInitializer]?.(); @@ -308,11 +306,9 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface } case 'attr': { - const - attr = info.name; + const attr = info.name; - let - unwatch: Function; + let unwatch: Function; if ('watch' in watchInfo) { unwatch = watchInfo.watch(attr, (value: object, oldValue: object) => { diff --git a/src/core/component/watch/helpers.ts b/src/core/component/watch/helpers.ts index a79504e05c..c2a28a6a48 100644 --- a/src/core/component/watch/helpers.ts +++ b/src/core/component/watch/helpers.ts @@ -141,8 +141,7 @@ export function attachDynamicWatcher( let destructor: Function; if (prop.type === 'mounted') { - let - watcher: Watcher; + let watcher: Watcher; if (Object.size(prop.path) > 0) { watcher = watch(prop.ctx, prop.path, watchOpts, wrapper); From a19c306319175c1883295d95f1ba4e837aaf61f9 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 21 Oct 2024 15:08:17 +0300 Subject: [PATCH 255/334] fix: fixed overriding --- src/core/component/accessor/index.ts | 10 +++++++-- .../decorators/computed/decorator.ts | 21 +++++++++++++------ .../decorators/default-value/decorator.ts | 6 +++--- .../component/decorators/method/decorator.ts | 8 +++---- .../component/decorators/prop/decorator.ts | 12 ++++++----- .../component/decorators/system/decorator.ts | 12 ++++++----- .../component/decorators/watch/decorator.ts | 4 ++-- src/core/component/meta/field.ts | 8 ++++++- 8 files changed, 53 insertions(+), 28 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index eb0b7a92f8..f52997fdb4 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -82,6 +82,10 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { for (const name in meta.accessors) { const accessor = meta.accessors[name]; + if (accessor == null) { + continue; + } + const tiedWith = tiedFields[name]; // In the `tiedFields` dictionary, @@ -92,7 +96,6 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { delete tiedFields[name]; const canSkip = - accessor == null || component[name] != null || !SSR && isFunctional && accessor.functional === false; @@ -167,6 +170,10 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { for (const name in meta.computedFields) { const computed = meta.computedFields[name]; + if (computed == null) { + continue; + } + const tiedWith = tiedFields[name]; // In the `tiedFields` dictionary, @@ -177,7 +184,6 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { delete tiedFields[name]; const canSkip = - computed == null || component[name] != null || computed.cache === 'auto' || !SSR && isFunctional && computed.functional === false; diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts index 0b5412d1e5..6120411ced 100644 --- a/src/core/component/decorators/computed/decorator.ts +++ b/src/core/component/decorators/computed/decorator.ts @@ -37,9 +37,18 @@ export function computed(params?: DecoratorComputed): PartDecorator { return createComponentDecorator3(({meta}, accessorName) => { params = {...params}; - delete meta.props[accessorName]; - delete meta.fields[accessorName]; - delete meta.systemFields[accessorName]; + if (meta.props[accessorName] != null) { + meta.props[accessorName] = undefined; + delete meta.component.props[accessorName]; + } + + if (meta.fields[accessorName] != null) { + meta.fields[accessorName] = undefined; + } + + if (meta.systemFields[accessorName] != null) { + meta.systemFields[accessorName] = undefined; + } let cluster: 'accessors' | 'computedFields' = 'accessors'; @@ -47,7 +56,7 @@ export function computed(params?: DecoratorComputed): PartDecorator { params.cache === true || params.cache === 'auto' || params.cache === 'forever' || - params.cache !== false && (Object.isArray(params.dependencies) || accessorName in meta.computedFields) + params.cache !== false && (Object.isArray(params.dependencies) || meta.computedFields[accessorName] != null) ) { cluster = 'computedFields'; } @@ -60,8 +69,8 @@ export function computed(params?: DecoratorComputed): PartDecorator { }; const needOverrideComputed = cluster === 'accessors' ? - accessorName in meta.computedFields : - !('cache' in params) && accessorName in meta.accessors; + meta.computedFields[accessorName] != null : + !('cache' in params) && meta.accessors[accessorName] != null; if (needOverrideComputed) { const computed = meta.computedFields[accessorName]; diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index 8bc442450e..42823acbf9 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -43,13 +43,13 @@ export function defaultValue(getter: unknown): PartDecorator { return createComponentDecorator3(({meta}, key) => { const isFunction = Object.isFunction(getter); - if (key in meta.props) { + if (meta.props[key] != null) { regProp(key, {default: getter}, meta); - } else if (key in meta.fields) { + } else if (meta.fields[key] != null) { regField(key, 'fields', isFunction ? {init: getter} : {default: getter}, meta); - } else if (key in meta.systemFields) { + } else if (meta.systemFields[key] != null) { regField(key, 'systemFields', isFunction ? {init: getter} : {default: getter}, meta); } else if (isFunction) { diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index 172ed17817..789da06c2d 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -126,7 +126,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p for (const hookName in hooks) { const hook = hooks[hookName]; - if (isFunctional && hook.functional === false) { + if (hook == null || isFunctional && hook.functional === false) { continue; } @@ -174,8 +174,8 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p // Computed fields are cached by default if ( - name in meta.computedFields || - !(name in meta.accessors) && (tiedWith = props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) + meta.computedFields[name] != null || + meta.accessors[name] == null && (tiedWith = props[propKey] ?? fields[storeKey] ?? systemFields[storeKey]) ) { type = 'computedFields'; } @@ -198,7 +198,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p // we need to delete it to correct override if (field[name] != null) { Object.defineProperty(proto, name, defProp); - delete field[name]; + field[name] = undefined; } const diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 6a3f3a1018..293c51e5e9 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -66,15 +66,17 @@ export function regProp(propName: string, typeOrParams: Nullable): ComponentFieldIn // eslint-disable-next-line guard-for-in for (const name in fields) { - list.push([name, fields[name]!]); + const field = fields[name]; + + if (field == null) { + continue; + } + + list.push([name, field]); } return list.sort(([aName], [bName]) => { From 5b93191b7dde25b7e01aea6b57148ad10df79240 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 21 Oct 2024 15:09:46 +0300 Subject: [PATCH 256/334] chore: optimization --- src/core/component/meta/fill.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/component/meta/fill.ts b/src/core/component/meta/fill.ts index 4080c8e921..b23431fbec 100644 --- a/src/core/component/meta/fill.ts +++ b/src/core/component/meta/fill.ts @@ -62,8 +62,10 @@ export function fillMeta(meta: ComponentMeta, constructor: ComponentConstructor const {component} = meta; - meta.fieldInitializers = sortFields(meta.fields); - meta.systemFieldInitializers = sortFields(meta.systemFields); + if (isFirstFill) { + meta.fieldInitializers = sortFields(meta.fields); + meta.systemFieldInitializers = sortFields(meta.systemFields); + } for (const init of meta.metaInitializers.values()) { init(meta); From bdd54c1228a7c59bc5b10abc4119d220bf805159 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 21 Oct 2024 15:11:09 +0300 Subject: [PATCH 257/334] chore: optimization --- src/core/component/meta/create.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index 86fd2eb840..1880faaae5 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -33,7 +33,7 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { const {constructor} = this; if (!constructor.hasOwnProperty(INSTANCE)) { - Object.defineProperty(constructor, INSTANCE, {value: new constructor()}); + Object.defineProperty(constructor, INSTANCE, {value: Object.create(constructor.prototype)}); } return constructor[INSTANCE]; From 05fb9ec30ef45afa2b5503dbbe1605267f49c94a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 21 Oct 2024 16:20:06 +0300 Subject: [PATCH 258/334] refactor: generate getters/setters using transformers --- .../resister-component-parts/index.js | 79 +++++++++++++++---- .../component/decorators/method/decorator.ts | 26 ------ 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/build/ts-transformers/resister-component-parts/index.js b/build/ts-transformers/resister-component-parts/index.js index 556ea25c90..3ff2341188 100644 --- a/build/ts-transformers/resister-component-parts/index.js +++ b/build/ts-transformers/resister-component-parts/index.js @@ -14,6 +14,7 @@ const ts = require('typescript'); * @typedef {import('typescript').Transformer} Transformer * @typedef {import('typescript').TransformationContext} TransformationContext * @typedef {import('typescript').Node} Node + * @typedef {import('typescript').ClassDeclaration} ClassDeclaration */ module.exports = resisterComponentDefaultValues; @@ -72,6 +73,8 @@ module.exports = resisterComponentDefaultValues; * ``` */ function resisterComponentDefaultValues(context) { + const {factory} = context; + let needImportDefaultValueDecorator = false, needImportMethodDecorator = false; @@ -94,25 +97,63 @@ function resisterComponentDefaultValues(context) { * A visitor for the AST node * * @param {Node} node - * @returns {Node} + * @returns {Node|ClassDeclaration} */ function visitor(node) { - if ( - ts.isPropertyDeclaration(node) && - ts.hasInitializer(node) && - !node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword) && - isComponentClass(node.parent, 'component') - ) { - needImportDefaultValueDecorator = true; - return addDefaultValueDecorator(context, node); - } - - if ( - (ts.isMethodDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) && - isComponentClass(node.parent, 'component') - ) { - needImportMethodDecorator = true; - return addMethodDecorator(context, node); + if (isComponentClass(node, 'component') && node.members != null) { + const newMembers = node.members.flatMap((node) => { + if ( + ts.isPropertyDeclaration(node) && + ts.hasInitializer(node) && + !node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword) + ) { + needImportDefaultValueDecorator = true; + return addDefaultValueDecorator(context, node); + } + + const name = node.name.getText(); + + const + isGetter = ts.isGetAccessorDeclaration(node), + isSetter = !isGetter && ts.isSetAccessorDeclaration(node); + + if (isGetter || isSetter || ts.isMethodDeclaration(node)) { + needImportMethodDecorator = true; + node = addMethodDecorator(context, node); + } + + if (isGetter || isSetter) { + const + postfix = isGetter ? 'Getter' : 'Setter', + methodName = context.factory.createStringLiteral(name + postfix); + + const method = factory.createMethodDeclaration( + undefined, + undefined, + undefined, + methodName, + undefined, + undefined, + [], + undefined, + node.body + ); + + return [node, method]; + } + + return node; + }); + + return factory.updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + newMembers + ); } return ts.visitEachChild(node, visitor, context); @@ -304,6 +345,10 @@ function addDecoratorImport(name, path, context, node) { function isComponentClass(node) { const {decorators} = node; + if (!ts.isClassDeclaration(node)) { + return false; + } + const getDecoratorName = (decorator) => ( decorator.expression && decorator.expression.expression && diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index 789da06c2d..ee42131187 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -210,32 +210,6 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p // eslint-disable-next-line @v4fire/unbound-method get = desc.get ?? old?.get; - // To use `super` within the setter, we also create a new method with a name `${key}Setter` - if (set != null) { - const methodName = `${name}Setter`; - proto[methodName] = set; - - methods[methodName] = { - src, - fn: set, - watchers: {}, - hooks: {} - }; - } - - // To using `super` within the getter, we also create a new method with a name `${key}Getter` - if (get != null) { - const methodName = `${name}Getter`; - proto[methodName] = get; - - methods[methodName] = { - src, - fn: get, - watchers: {}, - hooks: {} - }; - } - let accessor: ComponentAccessor; if (store.hasOwnProperty(name)) { From 8e26952d8a074c42e1439aed8b9fe96e0415c39c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Mon, 21 Oct 2024 18:54:19 +0300 Subject: [PATCH 259/334] fix: fixed modifier initializing --- src/components/super/i-block/mods/index.ts | 2 +- .../super/i-block/modules/mods/index.ts | 125 +++++++++++------- 2 files changed, 80 insertions(+), 47 deletions(-) diff --git a/src/components/super/i-block/mods/index.ts b/src/components/super/i-block/mods/index.ts index 75390ea9f2..937f6bfee4 100644 --- a/src/components/super/i-block/mods/index.ts +++ b/src/components/super/i-block/mods/index.ts @@ -23,7 +23,7 @@ export * from 'components/super/i-block/mods/interface'; @component({partial: 'iBlock'}) export default abstract class iBlockMods extends iBlockEvent { - @system({merge: mergeMods, init: initMods}) + @system({atom: true, merge: mergeMods, init: initMods}) override readonly mods!: ModsDict; override get sharedMods(): CanNull { diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 63bd94e5d7..6f84ef8bd1 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -25,14 +25,18 @@ export * from 'components/super/i-block/modules/mods/interface'; * @param component */ export function initMods(component: iBlock['unsafe']): ModsDict { - const declMods = component.meta.component.mods; + const + declMods = component.meta.component.mods, + resolveModVal = (val: unknown) => val != null ? String(val) : undefined; const attrMods: Array<[string, () => CanUndef]> = [], - modVal = (val: unknown) => val != null ? String(val) : undefined; + attrNames = Object.keys(component.$attrs); - Object.keys(component.$attrs).forEach((attrName) => { - const modName = attrName.camelize(false); + for (let i = 0; i < attrNames.length; i++) { + const + attrName = attrNames[i], + modName = attrName.camelize(false); if (modName in declMods) { let el: Nullable; @@ -44,7 +48,7 @@ export function initMods(component: iBlock['unsafe']): ModsDict { el.removeAttribute(attrName); } - void component.setMod(modName, modVal(attrs[attrName])); + void component.setMod(modName, resolveModVal(attrs[attrName])); }); component.meta.hooks['before:mounted'].push({ @@ -57,9 +61,9 @@ export function initMods(component: iBlock['unsafe']): ModsDict { } }); - attrMods.push([modName, () => modVal(component.$attrs[attrName])]); + attrMods.push([modName, () => resolveModVal(component.$attrs[attrName])]); } - }); + } return Object.cast(component.sync.link(link)); @@ -69,47 +73,67 @@ export function initMods(component: iBlock['unsafe']): ModsDict { mods = isModsInitialized ? component.mods : {...declMods}; if (propMods != null) { - Object.entries(propMods).forEach(([key, val]) => { - if (val != null || mods[key] == null) { - mods[key] = modVal(val); + const propNames = Object.keys(propMods); + + for (let i = 0; i < propNames.length; i++) { + const + propName = propNames[i], + propVal = propMods[propNames[i]]; + + if (propVal != null || mods[propName] == null) { + mods[propName] = resolveModVal(propVal); } - }); + } } - attrMods.forEach(([name, getter]) => { - const val = getter(); + for (let i = 0; i < attrMods.length; i++) { + const [attrName, getAttrValue] = attrMods[i]; - if (isModsInitialized || val != null) { - mods[name] = val; + const attrVal = getAttrValue(); + + if (isModsInitialized || attrVal != null) { + mods[attrName] = attrVal; } - }); + } const {experiments} = component.r.remoteState; if (Object.isArray(experiments)) { - experiments.forEach((exp) => { - const experimentMods = exp.meta?.mods; + for (let i = 0; i < experiments.length; i++) { + const + exp = experiments[i], + expMods = exp.meta?.mods; - if (!Object.isDictionary(experimentMods)) { - return; + if (!Object.isDictionary(expMods)) { + continue; } - Object.entries(experimentMods).forEach(([name, val]) => { - if (val != null || mods[name] == null) { - mods[name] = modVal(val); + const expModNames = Object.keys(expMods); + + for (let i = 0; i < expModNames.length; i++) { + const + modName = expModNames[i], + modVal = expMods[modName]; + + if (modVal != null || mods[modName] == null) { + mods[modName] = resolveModVal(modVal); } - }); - }); + } + } } - Object.entries(mods).forEach(([name, val]) => { - val = modVal(mods[name]); - mods[name] = val; + const modNames = Object.keys(mods); + + for (let i = 0; i < modNames.length; i++) { + const modName = modNames[i]; + + const modVal = resolveModVal(mods[modName]); + mods[modName] = modVal; if (component.hook !== 'beforeDataCreate') { - void component.setMod(name, val); + void component.setMod(modName, modVal); } - }); + } return mods; } @@ -147,15 +171,19 @@ export function mergeMods( return; } + const modsProp = getExpandedModsProp(component); + const - modsProp = getExpandedModsProp(component), - mods = {...oldComponent.mods}; + mods = {...oldComponent.mods}, + modNames = Object.keys(mods); - Object.keys(mods).forEach((key) => { - if (component.sync.syncModCache[key] != null) { - delete mods[key]; + for (let i = 0; i < modNames.length; i++) { + const modName = modNames[i]; + + if (component.sync.syncModCache[modName] != null) { + delete mods[modName]; } - }); + } if (Object.fastCompare(modsProp, getExpandedModsProp(oldComponent))) { l.sync(mods); @@ -200,27 +228,32 @@ export function mergeMods( export function getReactiveMods(component: iBlock): Readonly { const watchMods = {}, - watchers = component.field.get('reactiveModsStore')!, - systemMods = component.mods; + watchers = component.field.get('reactiveModsStore')!; + + const modNames = Object.keys(component.mods); - Object.entries(systemMods).forEach(([name, val]) => { - if (name in watchers) { - watchMods[name] = val; + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + modVal = component.mods[modName]; + + if (modName in watchers) { + watchMods[modName] = modVal; } else { - Object.defineProperty(watchMods, name, { + Object.defineProperty(watchMods, modName, { configurable: true, enumerable: true, get: () => { - if (!(name in watchers)) { - Object.getPrototypeOf(watchers)[name] = val; + if (!(modName in watchers)) { + Object.getPrototypeOf(watchers)[modName] = modVal; } - return watchers[name]; + return watchers[modName]; } }); } - }); + } return Object.freeze(watchMods); } From 1236082be209d543b0627b51678500adbf213b16 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 22 Oct 2024 14:44:16 +0300 Subject: [PATCH 260/334] fix: fixes after refactoring --- src/components/base/b-tree/b-tree.ts | 2 +- .../component/decorators/default-value/decorator.ts | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/base/b-tree/b-tree.ts b/src/components/base/b-tree/b-tree.ts index 62b603e4f4..65106bc92a 100644 --- a/src/components/base/b-tree/b-tree.ts +++ b/src/components/base/b-tree/b-tree.ts @@ -136,7 +136,7 @@ class bTree extends iTreeProps implements iActiveItems, iFoldable { return normalizeItems.call(o, val); })) - protected itemsStore: this['Items'] = []; + protected itemsStore!: this['Items']; /** @inheritDoc */ declare protected readonly $refs: iData['$refs'] & { diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index 42823acbf9..2b7000d0b1 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -47,10 +47,18 @@ export function defaultValue(getter: unknown): PartDecorator { regProp(key, {default: getter}, meta); } else if (meta.fields[key] != null) { - regField(key, 'fields', isFunction ? {init: getter} : {default: getter}, meta); + const params = isFunction ? + {init: getter, default: undefined} : + {init: undefined, default: getter}; + + regField(key, 'fields', params, meta); } else if (meta.systemFields[key] != null) { - regField(key, 'systemFields', isFunction ? {init: getter} : {default: getter}, meta); + const params = isFunction ? + {init: getter, default: undefined} : + {init: undefined, default: getter}; + + regField(key, 'systemFields', params, meta); } else if (isFunction) { Object.defineProperty(meta.constructor.prototype, key, { From 827e09269eb6dd4ec02f0bd00075a4a1ed6409fb Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 22 Oct 2024 15:27:23 +0300 Subject: [PATCH 261/334] fix: provide parameters to setter --- build/ts-transformers/resister-component-parts/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/ts-transformers/resister-component-parts/index.js b/build/ts-transformers/resister-component-parts/index.js index 3ff2341188..c030bd328f 100644 --- a/build/ts-transformers/resister-component-parts/index.js +++ b/build/ts-transformers/resister-component-parts/index.js @@ -134,7 +134,7 @@ function resisterComponentDefaultValues(context) { methodName, undefined, undefined, - [], + node.parameters, undefined, node.body ); From a53ceb4dabd44fafe69fc3dbcbb6eae9a323121a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 22 Oct 2024 15:46:52 +0300 Subject: [PATCH 262/334] chore: added FIXME & stylish fixes --- src/components/base/b-tree/test/unit/active-items.ts | 1 - src/core/component/render/helpers/test/unit/flags.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/base/b-tree/test/unit/active-items.ts b/src/components/base/b-tree/test/unit/active-items.ts index 284be3aa4f..121a191021 100644 --- a/src/components/base/b-tree/test/unit/active-items.ts +++ b/src/components/base/b-tree/test/unit/active-items.ts @@ -352,7 +352,6 @@ test.describe(' active items API', () => { await expectActive(true, activeNodes); await expectActive(false, inactiveNodes); - }); test('should unset the previous active items with `unsetPrevious = true`', async ({page}) => { diff --git a/src/core/component/render/helpers/test/unit/flags.ts b/src/core/component/render/helpers/test/unit/flags.ts index 6ce0c16d52..b1a0bee0f0 100644 --- a/src/core/component/render/helpers/test/unit/flags.ts +++ b/src/core/component/render/helpers/test/unit/flags.ts @@ -30,6 +30,7 @@ test.describe('core/component/render/helpers/flags', () => { await renderDummy(page, true); const vnode = page.getByTestId('vnode'); + // FIXME: don't use private API const patchFlag = await vnode.evaluate( (ctx) => (<{__vnode?: VNode}>ctx).__vnode?.patchFlag ?? 0 ); From 95ddc36c7ab13a572f571f2e616b31805824e67a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 22 Oct 2024 16:20:49 +0300 Subject: [PATCH 263/334] fix: fixes after refactoring --- build/snakeskin/default-filters.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/snakeskin/default-filters.js b/build/snakeskin/default-filters.js index ec11ad7519..4bde7b29a3 100644 --- a/build/snakeskin/default-filters.js +++ b/build/snakeskin/default-filters.js @@ -83,9 +83,9 @@ function tagFilter({name: tag, attrs = {}}, _, rootTag, forceRenderAsVNode, tplN componentName = attrs[TYPE_OF]; } else if (tag === 'component') { - if (attrs[':instance-of']) { - componentName = attrs[':instance-of'][0].camelize(false); - delete attrs[':instance-of']; + if (attrs[':instanceOf']) { + componentName = attrs[':instanceOf'][0].camelize(false); + delete attrs[':instanceOf']; } else { componentName = 'iBlock'; From 4d3e763af04a79cde8006fc7e5acce484f4c8d47 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 23 Oct 2024 13:49:17 +0300 Subject: [PATCH 264/334] fix: fixed prop normalization --- build/snakeskin/default-filters.js | 58 ++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/build/snakeskin/default-filters.js b/build/snakeskin/default-filters.js index 4bde7b29a3..aae98cf36e 100644 --- a/build/snakeskin/default-filters.js +++ b/build/snakeskin/default-filters.js @@ -59,26 +59,13 @@ Snakeskin.importFilters({ }); function tagFilter({name: tag, attrs = {}}, _, rootTag, forceRenderAsVNode, tplName, cursor) { - const needCamelize = /^(:[^-]|@)/; - - Object.entries(attrs).forEach(([key, attr]) => { - if (isStaticV4Prop.test(key)) { - // Since HTML is not case-sensitive, the name can be written differently. - // We will explicitly normalize the name to the most popular format for HTML notation. - // For Vue attributes such as `:` and `@`, we convert the prop to camelCase format. - const normalizedKey = needCamelize.test(key) ? - key.camelize(false) : - key.dasherize(); - - if (normalizedKey !== key) { - delete attrs[key]; - attrs[normalizedKey] = attr; - } - } - }); - let componentName; + if (attrs[':instance-of']) { + attrs[':instanceOf'] = attrs[':instance-of']; + delete attrs[':instance-of']; + } + if (attrs[TYPE_OF]) { componentName = attrs[TYPE_OF]; @@ -97,6 +84,41 @@ function tagFilter({name: tag, attrs = {}}, _, rootTag, forceRenderAsVNode, tplN const component = componentParams[componentName]; + Object.entries(attrs).forEach(([key, attr]) => { + if (isStaticV4Prop.test(key)) { + // Since HTML is not case-sensitive, the name can be written differently. + // We will explicitly normalize the name to the most popular format for HTML notation. + // For Vue component attributes such as `:` and `@`, we convert the prop to camelCase format. + let normalizedKey; + + if (component) { + if (key.startsWith('@')) { + normalizedKey = key.camelize(false); + + } else if (key.startsWith('v-on:')) { + normalizedKey = key.replace(/^v-on:([^.[]+)(.*)/, (_, event, rest) => + `v-on:${event.camelize(false)}${rest}`); + + } else if (key.startsWith(':') && !key.startsWith(':-') && !key.startsWith(':v-')) { + const camelizedKey = key.camelize(false); + + if (component.props[camelizedKey.slice(1)]) { + normalizedKey = camelizedKey; + } + } + } + + if (!normalizedKey) { + normalizedKey = key.dasherize(); + } + + if (normalizedKey !== key) { + delete attrs[key]; + attrs[normalizedKey] = attr; + } + } + }); + if (isSmartComponent(component)) { attrs[SMART_PROPS] = component.functional; } From 44d13298030e13fcb7e3edffa64097434f52734f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 23 Oct 2024 14:54:59 +0300 Subject: [PATCH 265/334] fix: fixes after refactoring --- src/core/component/decorators/prop/decorator.ts | 2 ++ src/core/component/decorators/system/decorator.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 293c51e5e9..61ab7dc239 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -68,6 +68,7 @@ export function regProp(propName: string, typeOrParams: Nullable Date: Wed, 23 Oct 2024 14:55:34 +0300 Subject: [PATCH 266/334] chore: reverted methods from prototype for functional components --- .../component/functional/context/create.ts | 6 ++-- src/core/component/method/README.md | 4 +++ src/core/component/method/index.ts | 28 ++++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/core/component/functional/context/create.ts b/src/core/component/functional/context/create.ts index b6b4001759..904093c13c 100644 --- a/src/core/component/functional/context/create.ts +++ b/src/core/component/functional/context/create.ts @@ -10,7 +10,9 @@ import * as init from 'core/component/init'; import { saveRawComponentContext } from 'core/component/context'; import { forkMeta, ComponentMeta } from 'core/component/meta'; + import { initProps } from 'core/component/prop'; +import { attachMethodsFromMeta } from 'core/component/method'; import type { ComponentInterface } from 'core/component/interface'; import type { VirtualContextOptions } from 'core/component/functional/interface'; @@ -89,8 +91,6 @@ export function createVirtualContext( } const virtualCtx = Object.cast({ - __proto__: meta.component.methods, - componentName: meta.componentName, render: meta.component.render, @@ -141,6 +141,8 @@ export function createVirtualContext( saveToStore: true }); + attachMethodsFromMeta(virtualCtx); + init.beforeCreateState(virtualCtx, meta, { implementEventAPI: true }); diff --git a/src/core/component/method/README.md b/src/core/component/method/README.md index 29e3f23a48..f33a0c4596 100644 --- a/src/core/component/method/README.md +++ b/src/core/component/method/README.md @@ -4,6 +4,10 @@ This module offers an API for initializing component methods on a component inst ## Functions +### attachMethodsFromMeta + +This function attaches methods to the passed component instance, taken from its associated metaobject. + ### callMethodFromComponent This function invokes a specific method from the passed component instance. diff --git a/src/core/component/method/index.ts b/src/core/component/method/index.ts index 564203442b..e59db173eb 100644 --- a/src/core/component/method/index.ts +++ b/src/core/component/method/index.ts @@ -11,7 +11,33 @@ * @packageDocumentation */ -import type { ComponentInterface } from 'core/component/interface'; +import type { ComponentInterface, UnsafeComponentInterface } from 'core/component/interface'; + +/** + * Attaches methods to the passed component instance, taken from its associated metaobject + * @param component + */ +export function attachMethodsFromMeta(component: ComponentInterface): void { + const {meta, meta: {component: {methods}}} = Object.cast(component); + + const methodNames = Object.keys(methods); + + for (let i = 0; i < methodNames.length; i++) { + const + methodName = methodNames[i], + method = methods[methodName]; + + if (method == null) { + continue; + } + + component[methodName] = method.bind(component); + } + + if (meta.params.functional === true) { + component.render = Object.cast(meta.component.render); + } +} /** * Invokes a specific method from the passed component instance From b750eb208b35a2e5351c78f3e8ed720615f6bc8d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 23 Oct 2024 16:33:02 +0300 Subject: [PATCH 267/334] fix: don't provide internal props --- .../base/b-dynamic-page/b-dynamic-page.ss | 8 +++++++ .../base/b-dynamic-page/b-dynamic-page.ts | 21 +++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ss b/src/components/base/b-dynamic-page/b-dynamic-page.ss index 3321b46ea7..5bf2fe35d3 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ss +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ss @@ -20,10 +20,18 @@ : graph = include('build/graph/component-params') ? Object.assign(attrs, graph.getComponentPropAttrs(self.name(PARENT_TPL_NAME))) + ? delete attrs[':is'] ? delete attrs[':keepAlive'] ? delete attrs[':dispatching'] + ? delete attrs[':componentId'] + ? delete attrs[':getRoot'] + ? delete attrs[':getParent'] + + ? delete attrs[':getPassedHandlers'] + ? delete attrs[':getPassedProps'] + < template v-for = el in asyncRender.iterate(renderIterator, {filter: renderFilter, group: registerRenderGroup}) < component.&__component & v-if = !pageTakenFromCache && page != null | diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index 24fdf7da22..ab157eae6f 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -424,8 +424,7 @@ export default class bDynamicPage extends iDynamicPage { }); } else { - const - pageComponentFromCache = pageElFromCache.component; + const pageComponentFromCache = pageElFromCache.component; if (pageComponentFromCache != null) { pageComponentFromCache.activate(); @@ -477,8 +476,7 @@ export default class bDynamicPage extends iDynamicPage { return loopbackStrategy; } - const - {exclude, include} = this; + const {exclude, include} = this; if (exclude != null) { if (Object.isFunction(exclude)) { @@ -491,11 +489,9 @@ export default class bDynamicPage extends iDynamicPage { } } - let - cacheKey = page; + let cacheKey = page; - const - globalCache = this.keepAliveCache.global!; + const globalCache = this.keepAliveCache.global!; const globalStrategy: KeepAliveStrategy = { isLoopback: false, @@ -507,8 +503,7 @@ export default class bDynamicPage extends iDynamicPage { if (include != null) { if (Object.isFunction(include)) { - const - res = include(page, route, this); + const res = include(page, route, this); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (res == null || res === false) { @@ -555,11 +550,9 @@ export default class bDynamicPage extends iDynamicPage { * @param cache */ protected addClearListenersToCache>(cache: T): T { - const - wrappedCache = addEmitter>(cache); + const wrappedCache = addEmitter>(cache); - let - instanceCache: WeakMap = new WeakMap(); + let instanceCache: WeakMap = new WeakMap(); wrappedCache.subscribe('set', cache, changeCountInMap(0, 1)); wrappedCache.subscribe('remove', cache, changeCountInMap(1, -1)); From 9e06bb9db47ca3515bbe4c9a41180ad91743a079 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 23 Oct 2024 18:28:08 +0300 Subject: [PATCH 268/334] fix: fixes after refactoring --- src/core/component/decorators/prop/decorator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 61ab7dc239..922d0c8542 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -55,8 +55,8 @@ export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { */ export function regProp(propName: string, typeOrParams: Nullable, meta: ComponentMeta): void { const params: DecoratorProp = Object.isFunction(typeOrParams) || Object.isArray(typeOrParams) ? - {type: typeOrParams, forceUpdate: true} : - {forceUpdate: true, ...typeOrParams}; + {type: typeOrParams} : + {...typeOrParams}; let prop: ComponentProp; From ea8ba1b2aa91972df8df8fe5cc940d6e1a0a5385 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 24 Oct 2024 15:57:01 +0300 Subject: [PATCH 269/334] fix: fixes after refactoring --- src/components/friends/provide/classes.ts | 70 ++++++++++++++--------- src/components/friends/provide/mods.ts | 17 +++--- src/components/friends/provide/names.ts | 3 +- src/components/friends/sync/mod.ts | 12 ++-- 4 files changed, 60 insertions(+), 42 deletions(-) diff --git a/src/components/friends/provide/classes.ts b/src/components/friends/provide/classes.ts index 360527803d..117d25ced3 100644 --- a/src/components/friends/provide/classes.ts +++ b/src/components/friends/provide/classes.ts @@ -84,9 +84,15 @@ export function classes( classes ??= {}; - const map = {}; + const + classNames = Object.keys(classes), + classesMap = {}; + + for (let i = 0; i < classNames.length; i++) { + const innerEl = classNames[i]; + + let outerEl = classes[innerEl]; - Object.entries(classes).forEach(([innerEl, outerEl]) => { if (outerEl === true) { outerEl = innerEl; @@ -98,18 +104,12 @@ export function classes( outerEl[i] = innerEl; } } - - outerEl.forEach((el, i) => { - if (el === true) { - outerEl![i] = innerEl; - } - }); } - map[innerEl.dasherize()] = fullElementName.apply(this, Array.toArray(componentName, outerEl)); - }); + classesMap[innerEl.dasherize()] = fullElementName.apply(this, Array.toArray(componentName, outerEl)); + } - return map; + return classesMap; } /** @@ -174,13 +174,19 @@ export function componentClasses( mods ??= {}; - const classes = [(fullComponentName).call(this, componentName)]; + const + modNames = Object.keys(mods), + classes = [(fullComponentName).call(this, componentName)]; + + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + modVal = mods[modName]; - Object.entries(mods).forEach(([key, val]) => { - if (val !== undefined) { - classes.push(fullComponentName.call(this, componentName, key, val)); + if (modVal !== undefined) { + classes.push(fullComponentName.call(this, componentName, modName, modVal)); } - }); + } return classes; } @@ -257,23 +263,35 @@ export function elementClasses( return []; } - const classes = componentId != null ? [componentId] : []; + const + elNames = Object.keys(els), + classes = componentId != null ? [componentId] : []; + + for (let i = 0; i < elNames.length; i++) { + const + elName = elNames[i], + elMods = els[elName]; - Object.entries(els).forEach(([el, mods]) => { classes.push( - (fullElementName).call(this, componentName, el) + (fullElementName).call(this, componentName, elName) ); - if (!Object.isDictionary(mods)) { - return; + if (!Object.isDictionary(elMods)) { + continue; } - Object.entries(mods).forEach(([key, val]) => { - if (val !== undefined) { - classes.push(fullElementName.call(this, componentName, el, key, val)); + const modNames = Object.keys(elMods); + + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + modVal = elMods[modName]; + + if (modVal !== undefined) { + classes.push(fullElementName.call(this, componentName, elName, modName, modVal)); } - }); - }); + } + } return classes; } diff --git a/src/components/friends/provide/mods.ts b/src/components/friends/provide/mods.ts index 362856c6c7..cea49888a2 100644 --- a/src/components/friends/provide/mods.ts +++ b/src/components/friends/provide/mods.ts @@ -6,11 +6,6 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -/** - * [[include:components/super/i-block/modules/provide/README.md]] - * @packageDocumentation - */ - import type Friend from 'components/friends/friend'; import type iBlock from 'components/super/i-block/i-block'; @@ -47,9 +42,15 @@ export function mods(this: Friend, mods?: Mods): CanNull { const resolvedMods = {...sharedMods}; if (mods != null) { - Object.entries(mods).forEach(([key, val]) => { - resolvedMods[key.dasherize()] = val != null ? String(val) : undefined; - }); + const modNames = Object.keys(mods); + + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + modVal = mods[modName]; + + resolvedMods[modName.dasherize()] = modVal != null ? String(modVal) : undefined; + } } return resolvedMods; diff --git a/src/components/friends/provide/names.ts b/src/components/friends/provide/names.ts index f18c59c831..fab959e836 100644 --- a/src/components/friends/provide/names.ts +++ b/src/components/friends/provide/names.ts @@ -202,8 +202,7 @@ export function fullElementName( modNameOrModValue?: string | unknown, modValue?: unknown ): string { - const - l = arguments.length; + const l = arguments.length; let componentName: string, diff --git a/src/components/friends/sync/mod.ts b/src/components/friends/sync/mod.ts index 7b6a17bee3..eee035d46c 100644 --- a/src/components/friends/sync/mod.ts +++ b/src/components/friends/sync/mod.ts @@ -128,19 +128,19 @@ export function mod( const {path} = info; - let val: unknown; + let rawVal: unknown; if (path.includes('.')) { - val = that.field.get(info.originalPath); + rawVal = that.field.get(info.originalPath); } else { - val = info.type === 'field' ? that.field.getFieldsStore(info.ctx)[path] : info.ctx[path]; + rawVal = info.type === 'field' ? that.field.getFieldsStore(info.ctx)[path] : info.ctx[path]; } - val = (converter).call(that.component, val); + const modVal = (converter).call(that.component, rawVal); - if (val !== undefined) { - ctx.mods[modName] = String(val); + if (modVal !== undefined) { + ctx.mods[modName] = String(modVal); } } From 3986851cb147e9af8003ca641d974d632730dcba Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 14:34:31 +0300 Subject: [PATCH 270/334] chore: stylish fixes --- src/components/form/b-button/b-button.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/form/b-button/b-button.ts b/src/components/form/b-button/b-button.ts index c056359449..576a01b475 100644 --- a/src/components/form/b-button/b-button.ts +++ b/src/components/form/b-button/b-button.ts @@ -222,8 +222,7 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi default: { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (this.dataProviderProp != null && (this.dataProviderProp !== 'Provider' || this.href != null)) { - let - {dataProvider} = this; + let {dataProvider} = this; if (dataProvider == null) { throw new ReferenceError('Missing data provider to send data'); @@ -242,8 +241,9 @@ class bButton extends iButtonProps implements iOpenToggle, iVisible, iWidth, iSi // Form attribute fix for MS Edge && IE } else if (this.form != null && this.type === 'submit') { e.preventDefault(); + const form = this.dom.getComponent(`#${this.form}`); - form && await form.submit(); + await form?.submit(); } await this.toggle(); From 35ff0dfe7f6511357e16b17da68483ce6e15c814 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:07:16 +0300 Subject: [PATCH 271/334] chore: stylish fixes --- src/components/friends/sync/class.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/friends/sync/class.ts b/src/components/friends/sync/class.ts index ec7e15d9fd..16272b55c8 100644 --- a/src/components/friends/sync/class.ts +++ b/src/components/friends/sync/class.ts @@ -61,17 +61,17 @@ interface Sync { @fakeMethods('object') class Sync extends Friend { /** - * Cache of functions to synchronize modifiers + * A cache of functions for synchronizing modifiers */ readonly syncModCache!: Dictionary; /** - * Cache for links + * A cache for storing links */ protected readonly linksCache!: Dictionary; /** - * The index of the last added link + * An index of the last added link */ protected lastSyncIndex: number = 0; From 8ebbe158ded4c4b18fde128dd14134f84d88fa0e Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:29:55 +0300 Subject: [PATCH 272/334] fix: theme initialization --- src/components/super/i-block/state/index.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 1724fee1fc..ff641e659b 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -609,21 +609,6 @@ export default abstract class iBlockState extends iBlockMods { this.syncRouterState = i.syncRouterState.bind(this); } - /** - * Initializes the theme modifier and attaches a listener to monitor changes of the theme - */ - @hook('created') - protected initThemeModListener(): void { - const theme = this.remoteState.theme.get(); - void this.setMod('theme', theme.value); - - this.async.on( - this.remoteState.theme.emitter, - 'theme.change', - (theme: Theme) => this.setMod('theme', theme.value) - ); - } - /** * Stores a boolean flag in the hydrationStore during SSR, * which determines whether the content of components should be rendered during hydration @@ -657,6 +642,9 @@ export default abstract class iBlockState extends iBlockMods { const v = this.stage; return v == null ? v : String(v); }); + + this.sync.mod('theme', 'remoteState.theme.emitter:theme.change', {immediate: true}, (theme?: Theme) => + theme != null ? theme.value : this.remoteState.theme.get()); } /** From 84fc757280ba610f98d1f35285ac9f683f341cb1 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:35:15 +0300 Subject: [PATCH 273/334] fix: merging modifiers --- .../super/i-block/modules/mods/index.ts | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 6f84ef8bd1..7e42944298 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -156,6 +156,8 @@ export function mergeMods( link?: string ): void { if (link == null) { + // @ts-ignore (readonly) + component.mods = {...oldComponent.mods}; return; } @@ -171,19 +173,9 @@ export function mergeMods( return; } - const modsProp = getExpandedModsProp(component); - const - mods = {...oldComponent.mods}, - modNames = Object.keys(mods); - - for (let i = 0; i < modNames.length; i++) { - const modName = modNames[i]; - - if (component.sync.syncModCache[modName] != null) { - delete mods[modName]; - } - } + modsProp = getExpandedModsProp(component), + mods = {...oldComponent.mods}; if (Object.fastCompare(modsProp, getExpandedModsProp(oldComponent))) { l.sync(mods); @@ -197,27 +189,27 @@ export function mergeMods( return {}; } - const modsProp = component.$props[link]; + const modsProp = component[link]; if (!Object.isDictionary(modsProp)) { return {}; } const - declMods = component.meta.component.mods, - res = {...modsProp}; + declaredMods = component.meta.component.mods, + expandedModsProp = {...modsProp}; component.getPassedProps?.().forEach((name) => { - if (name in declMods) { + if (name in declaredMods) { const attr = component.$attrs[name]; if (attr != null) { - res[name] = attr; + expandedModsProp[name] = attr; } } }); - return res; + return expandedModsProp; } } From 82394d3dfe94b7a544626696ba6d0f995c42c97d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:35:35 +0300 Subject: [PATCH 274/334] fix: avoid Vue warnings --- src/components/super/i-block/modules/lfc/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/modules/lfc/index.ts b/src/components/super/i-block/modules/lfc/index.ts index dd7f9f2898..4257dbfcbe 100644 --- a/src/components/super/i-block/modules/lfc/index.ts +++ b/src/components/super/i-block/modules/lfc/index.ts @@ -113,7 +113,7 @@ export default class Lfc extends Friend { * ``` */ execCbAfterBlockReady(cb: Cb, opts?: AsyncOptions): CanUndef> { - if (this.ctx.block) { + if ('block' in this.ctx) { if (statuses[this.componentStatus] >= 0) { return cb.call(this.component); } From 45b6b27195f80417dca5b8b9daf4b8b0490ce466 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:36:24 +0300 Subject: [PATCH 275/334] fix: merging state --- .../component/functional/context/inherit.ts | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/core/component/functional/context/inherit.ts b/src/core/component/functional/context/inherit.ts index 9046a55274..d180082905 100644 --- a/src/core/component/functional/context/inherit.ts +++ b/src/core/component/functional/context/inherit.ts @@ -31,55 +31,57 @@ export function inheritContext( // Additionally, we should not unmount the vnodes created within the component. parentCtx.$destroy({recursive: false, shouldUnmountVNodes: false}); - const linkedFields = {}; - const - props = ctx.$props, - parentProps = parentCtx.$props, - parentKeys = Object.keys(parentProps); + parentProps = parentCtx.getPassedProps?.(), + linkedFields = {}; - for (let i = 0; i < parentKeys.length; i++) { - const - prop = parentKeys[i], - linked = parentCtx.$syncLinkCache.get(prop); + if (parentProps != null) { + for (const prop of parentProps) { + const linked = parentCtx.$syncLinkCache.get(prop); - if (linked != null) { - const links = Object.values(linked); + if (linked != null) { + const links = Object.values(linked); - for (let i = 0; i < links.length; i++) { - const link = links[i]; + for (let i = 0; i < links.length; i++) { + const link = links[i]; - if (link != null) { - linkedFields[link.path] = prop; + if (link != null) { + linkedFields[link.path] = prop; + } } } } } - for (const cluster of [parentCtx.meta.systemFields, parentCtx.meta.fields]) { - const keys = Object.keys(cluster); + const parentMeta = parentCtx.meta; + + const clusters = [ + [parentMeta.systemFields, parentMeta.systemFieldInitializers], + [parentMeta.fields, parentMeta.fieldInitializers] + ]; - for (let i = 0; i < keys.length; i++) { + for (const [cluster, fields] of clusters) { + for (let i = 0; i < fields.length; i++) { const - name = keys[i], - field = cluster[name]; + fieldName = fields[i][0], + field = cluster[fieldName]; if (field == null) { continue; } - const link = linkedFields[name]; + const link = linkedFields[fieldName]; const - val = ctx[name], - oldVal = parentCtx[name]; + val = ctx[fieldName], + oldVal = parentCtx[fieldName]; const needMerge = - ctx.$modifiedFields[name] !== true && + ctx.$modifiedFields[fieldName] !== true && ( Object.isFunction(field.unique) ? - !Object.isTruly(field.unique(ctx, Object.cast(parentCtx))) : + !Object.isTruly(field.unique(ctx, parentCtx)) : !field.unique ) && @@ -87,30 +89,28 @@ export function inheritContext( ( link == null || - Object.fastCompare(props[link], parentProps[link]) + Object.fastCompare(ctx[link], parentCtx[link]) ); if (needMerge) { - if (Object.isTruly(field.merge)) { - if (field.merge === true) { - let newVal = oldVal; + if (field.merge === true) { + let newVal = oldVal; - if (Object.isDictionary(val) || Object.isDictionary(oldVal)) { - // eslint-disable-next-line prefer-object-spread - newVal = Object.assign({}, val, oldVal); + if (Object.isDictionary(val) || Object.isDictionary(oldVal)) { + // eslint-disable-next-line prefer-object-spread + newVal = Object.assign({}, val, oldVal); - } else if (Object.isArray(val) || Object.isArray(oldVal)) { - newVal = Object.assign([], val, oldVal); - } + } else if (Object.isArray(val) || Object.isArray(oldVal)) { + newVal = Object.assign([], val, oldVal); + } - ctx[name] = newVal; + ctx[fieldName] = newVal; - } else if (Object.isFunction(field.merge)) { - field.merge(ctx, Object.cast(parentCtx), name, link); - } + } else if (Object.isFunction(field.merge)) { + field.merge(ctx, parentCtx, fieldName, link); } else { - ctx[name] = parentCtx[name]; + ctx[fieldName] = parentCtx[fieldName]; } } } From a7bc3e8c7f5de46f6ff820c760490b62545b2bb7 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:36:40 +0300 Subject: [PATCH 276/334] chore: optimization --- src/core/component/prop/helpers.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index 83cfd08a32..dabf376f4f 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -37,27 +37,30 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { propValuesToUpdate: string[][] = []; unsafe.getPassedProps?.()?.forEach((attrName) => { + const prop = meta.props[attrName]; + + if ((prop == null || prop.forceUpdate) && !isPropGetter.test(attrName)) { + return; + } + const propPrefix = 'on:'; - if (meta.props[attrName]?.forceUpdate === false) { + if (prop != null) { const getterName = propPrefix + attrName; if (unsafe.$attrs[attrName] !== undefined && !Object.isFunction(unsafe.$attrs[getterName])) { throw new Error(`No accessors are defined for the prop "${attrName}". To set the accessors, pass them as ":${attrName} = propValue | @:${attrName} = createPropAccessors(() => propValue)()" or "v-attrs = {'@:${attrName}': createPropAccessors(() => propValue)}".`); } - } - - if (!isPropGetter.test(attrName)) { - return; - } - const propName = isPropGetter.replace(attrName); + } else { + const propName = isPropGetter.replace(attrName); - if (meta.props[propName]?.forceUpdate === false) { - propValuesToUpdate.push([propName, attrName]); + if (meta.props[propName]?.forceUpdate === false) { + propValuesToUpdate.push([propName, attrName]); - if (el instanceof Element) { - el.removeAttribute(propName); + if (el instanceof Element) { + el.removeAttribute(propName); + } } } }); From 03a519aadfa6f6cc0ae8842d37a4f6ed9ebf4dfd Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:37:07 +0300 Subject: [PATCH 277/334] chore: stylish fixes --- src/core/component/directives/attrs/index.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/core/component/directives/attrs/index.ts b/src/core/component/directives/attrs/index.ts index ee6d45a5ee..e47664ff49 100644 --- a/src/core/component/directives/attrs/index.ts +++ b/src/core/component/directives/attrs/index.ts @@ -67,8 +67,7 @@ ComponentEngine.directive('attrs', { r = ctx.$renderEngine.r; } - let - attrs = {...params.value}; + let attrs = {...params.value}; if (componentMeta != null) { attrs = normalizeComponentAttrs(attrs, vnode.dynamicProps, componentMeta)!; @@ -228,8 +227,7 @@ ComponentEngine.directive('attrs', { if (Object.isDictionary(dir)) { if (Object.isFunction(dir.beforeCreate)) { - const - newVnode = dir.beforeCreate(binding, vnode); + const newVnode = dir.beforeCreate(binding, vnode); if (newVnode != null) { vnode = newVnode; @@ -354,8 +352,7 @@ ComponentEngine.directive('attrs', { props: Dictionary = {}, componentMeta = ctx?.meta; - let - attrs = {...params.value}; + let attrs = {...params.value}; if (componentMeta != null) { attrs = normalizeComponentAttrs(attrs, null, componentMeta)!; From 184e46a408cd7b61232807b45b2bcc900ecfa381 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:38:33 +0300 Subject: [PATCH 278/334] fix: node can be null --- src/core/prelude/test-env/components/index.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/core/prelude/test-env/components/index.ts b/src/core/prelude/test-env/components/index.ts index b69d5f1af3..fc9866fe66 100644 --- a/src/core/prelude/test-env/components/index.ts +++ b/src/core/prelude/test-env/components/index.ts @@ -7,6 +7,7 @@ */ import { app, ComponentInterface } from 'core/component'; + import { registerComponent } from 'core/component/init'; import { render, create } from 'components/friends/vdom'; @@ -15,8 +16,7 @@ import type { ComponentElement } from 'components/super/i-static-page/i-static-p import { expandedParse } from 'core/prelude/test-env/components/json'; -const - createdComponents = Symbol('A set of created components'); +const createdComponents = Symbol('A set of created components'); globalThis.renderComponents = ( componentName: string, @@ -30,11 +30,9 @@ globalThis.renderComponents = ( } } - const - ID_ATTR = 'data-dynamic-component-id'; + const ID_ATTR = 'data-dynamic-component-id'; - const - ctx = >app.component; + const ctx = >app.component; if (ctx == null) { throw new ReferenceError('The root context for rendering is not defined'); @@ -91,13 +89,12 @@ globalThis.renderComponents = ( }; globalThis.removeCreatedComponents = () => { - const - components = globalThis[createdComponents]; + const components = globalThis[createdComponents]; if (Object.isSet(components)) { - Object.cast>(components).forEach((node) => { - node.component?.unsafe.$destroy(); - node.remove(); + Object.cast>>(components).forEach((node) => { + node?.component?.unsafe.$destroy(); + node?.remove(); }); components.clear(); From c907994e013ea39a03cd00118f772855318d61b0 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:38:47 +0300 Subject: [PATCH 279/334] fix: fixed the test --- src/components/super/i-block/test/unit/teleports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/test/unit/teleports.ts b/src/components/super/i-block/test/unit/teleports.ts index daa2e2c84a..38a0d75f45 100644 --- a/src/components/super/i-block/test/unit/teleports.ts +++ b/src/components/super/i-block/test/unit/teleports.ts @@ -45,7 +45,7 @@ test.describe(' using the root teleport', () => { const attrs = await target.evaluate((ctx) => ctx.unsafe.$refs.component.$el!.className); - test.expect(attrs).toBe('i-block-helper u1e705d34abc46a b-bottom-slide b-bottom-slide_opened_false b-bottom-slide_stick_true b-bottom-slide_events_false b-bottom-slide_height-mode_full b-bottom-slide_visible_false b-bottom-slide_theme_light b-bottom-slide_hidden_true'); + test.expect(attrs).toBe('i-block-helper u1e705d34abc46a b-bottom-slide b-bottom-slide_opened_false b-bottom-slide_stick_true b-bottom-slide_events_false b-bottom-slide_height-mode_full b-bottom-slide_theme_light b-bottom-slide_visible_false b-bottom-slide_hidden_true'); }); }); From c32cc5904f49eee57b344caf6ca2356cbb41f146 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 15:48:29 +0300 Subject: [PATCH 280/334] fix: theme initializing --- src/components/super/i-block/state/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index ff641e659b..1c505b9ced 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -644,7 +644,7 @@ export default abstract class iBlockState extends iBlockMods { }); this.sync.mod('theme', 'remoteState.theme.emitter:theme.change', {immediate: true}, (theme?: Theme) => - theme != null ? theme.value : this.remoteState.theme.get()); + (theme ?? this.remoteState.theme.get()).value); } /** From 79c66ec3470f4f18bbe2d2dd8b90cb808c295401 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 17:36:39 +0300 Subject: [PATCH 281/334] fix: fixed functional default values --- .../resister-component-parts/index.js | 16 +++++----------- .../decorators/default-value/decorator.ts | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/build/ts-transformers/resister-component-parts/index.js b/build/ts-transformers/resister-component-parts/index.js index c030bd328f..15fff00d83 100644 --- a/build/ts-transformers/resister-component-parts/index.js +++ b/build/ts-transformers/resister-component-parts/index.js @@ -174,10 +174,7 @@ function addDefaultValueDecorator(context, node) { let getter; - if (ts.isFunctionLike(defaultValue)) { - getter = defaultValue; - - } else if ( + if ( ts.isNumericLiteral(defaultValue) || ts.isBigIntLiteral(defaultValue) || ts.isStringLiteral(defaultValue) || @@ -189,13 +186,10 @@ function addDefaultValueDecorator(context, node) { getter = defaultValue; } else { - const getterValue = ts.isFunctionLike(defaultValue) ? - defaultValue : - - factory.createBlock( - [factory.createReturnStatement(defaultValue)], - true - ); + const getterValue = factory.createBlock( + [factory.createReturnStatement(defaultValue)], + true + ); getter = factory.createFunctionExpression( undefined, diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index 2b7000d0b1..3ecef063dc 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -44,7 +44,7 @@ export function defaultValue(getter: unknown): PartDecorator { const isFunction = Object.isFunction(getter); if (meta.props[key] != null) { - regProp(key, {default: getter}, meta); + regProp(key, {default: isFunction ? getter() : getter}, meta); } else if (meta.fields[key] != null) { const params = isFunction ? From 59d6edb83802cffcce9b6349e08ed5f9073c78c2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 17:41:46 +0300 Subject: [PATCH 282/334] fix: fixed functional default values --- src/core/component/decorators/default-value/decorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index 3ecef063dc..30646a1b74 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -65,7 +65,7 @@ export function defaultValue(getter: unknown): PartDecorator { configurable: true, enumerable: false, writable: true, - value: getter + value: getter() }); } }); From 535d98084e181d3f7ca087adf9b9747acddaaf2c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 25 Oct 2024 19:38:20 +0300 Subject: [PATCH 283/334] fix: fixed initializing of getter dependencies --- src/core/component/accessor/index.ts | 170 +++++++++++++++------------ 1 file changed, 92 insertions(+), 78 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index f52997fdb4..d82316b8d6 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -119,38 +119,45 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const {watchers, watchDependencies} = meta; - onCreated(this.hook, () => { - // If a computed property has a field or system field as a dependency - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - watchDependencies.get(name)?.forEach((dep) => { - const - path = Object.isArray(dep) ? dep.join('.') : String(dep), - info = getPropertyInfo(path, component); - - const needForceWatch = - (info.type === 'system' || info.type === 'field') && - - watchers[info.name] == null && - watchers[info.originalPath] == null && - watchers[info.path] == null; - - if (needForceWatch) { - this.$watch(info, {deep: true, immediate: true}, fakeHandler); - } - }); - - if (tiedWith != null) { - const needForceWatch = watchers[tiedWith] == null && accessor.dependencies?.length !== 0; + const deps = watchDependencies.get(name); - // If a computed property is tied with a field or system field + if (name !== 'hook' && (deps != null && deps.length > 0 || tiedWith != null)) { + onCreated(this.hook, () => { + // If a computed property has a field or system field as a dependency // and the host component does not have any watchers to this field, // we need to register a "fake" watcher to enforce watching - if (needForceWatch) { - this.$watch(tiedWith, {deep: true, immediate: true}, fakeHandler); + if (deps != null) { + for (let i = 0; i < deps.length; i++) { + const + dep = deps[i], + path = Object.isArray(dep) ? dep.join('.') : String(dep), + info = getPropertyInfo(path, component); + + const needForceWatch = + (info.type === 'system' || info.type === 'field') && + + watchers[info.name] == null && + watchers[info.originalPath] == null && + watchers[info.path] == null; + + if (needForceWatch) { + this.$watch(info, {deep: true, immediate: true}, fakeHandler); + } + } } - } - }); + + if (tiedWith != null) { + const needForceWatch = watchers[tiedWith] == null && accessor.dependencies?.length !== 0; + + // If a computed property is tied with a field or system field + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + if (needForceWatch) { + this.$watch(tiedWith, {deep: true, immediate: true}, fakeHandler); + } + } + }); + } } return accessor.get!.call(this); @@ -212,66 +219,73 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const {watchers, watchDependencies} = meta; - onCreated(this.hook, () => { - // If a computed property has a field or system field as a dependency - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - watchDependencies.get(name)?.forEach((dep) => { - const - path = Object.isArray(dep) ? dep.join('.') : String(dep), - info = getPropertyInfo(path, component); - - // If a getter already has a cached result and is used inside a template, - // it is not possible to track its effect, as the value is not recalculated. - // This can lead to a problem where one of the entities on which the getter depends is updated, - // but the template is not. - // To avoid this problem, we explicitly touch all dependent entities. - // For functional components, this problem does not exist, - // as no change in state can trigger their re-render. - if (!isFunctional && info.type !== 'system') { - effects.push(() => { - const store = info.type === 'field' ? getFieldsStore(Object.cast(info.ctx)) : info.ctx; - - if (info.path.includes('.')) { - void Object.get(store, path); + const deps = watchDependencies.get(name); - } else if (path in store) { - // @ts-ignore (effect) - void store[path]; + if (name !== 'hook' && (deps != null && deps.length > 0 || tiedWith != null)) { + onCreated(this.hook, () => { + // If a computed property has a field or system field as a dependency + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + if (deps != null) { + for (let i = 0; i < deps.length; i++) { + const + dep = deps[i], + path = Object.isArray(dep) ? dep.join('.') : String(dep), + info = getPropertyInfo(path, component); + + // If a getter already has a cached result and is used inside a template, + // it is not possible to track its effect, as the value is not recalculated. + // This can lead to a problem where one of the entities on which the getter depends is updated, + // but the template is not. + // To avoid this problem, we explicitly touch all dependent entities. + // For functional components, this problem does not exist, + // as no change in state can trigger their re-render. + if (!isFunctional && info.type !== 'system') { + effects.push(() => { + const store = info.type === 'field' ? getFieldsStore(Object.cast(info.ctx)) : info.ctx; + + if (info.path.includes('.')) { + void Object.get(store, path); + + } else if (path in store) { + // @ts-ignore (effect) + void store[path]; + } + }); } - }); - } - const needToForceWatching = - (info.type === 'system' || info.type === 'field') && + const needToForceWatching = + (info.type === 'system' || info.type === 'field') && - watchers[info.name] == null && - watchers[info.originalPath] == null && - watchers[info.path] == null; + watchers[info.name] == null && + watchers[info.originalPath] == null && + watchers[info.path] == null; - if (needToForceWatching) { - this.$watch(info, {deep: true, immediate: true}, fakeHandler); + if (needToForceWatching) { + this.$watch(info, {deep: true, immediate: true}, fakeHandler); + } + } } - }); - if (tiedWith != null) { - effects.push(() => { - if (tiedWith in this) { - // @ts-ignore (effect) - void this[tiedWith]; - } - }); + if (tiedWith != null) { + effects.push(() => { + if (tiedWith in this) { + // @ts-ignore (effect) + void this[tiedWith]; + } + }); - const needToForceWatching = watchers[tiedWith] == null && computed.dependencies?.length !== 0; + const needToForceWatching = watchers[tiedWith] == null && computed.dependencies?.length !== 0; - // If a computed property is tied with a field or system field - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching - if (needToForceWatching) { - this.$watch(tiedWith, {deep: true, immediate: true}, fakeHandler); + // If a computed property is tied with a field or system field + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching + if (needToForceWatching) { + this.$watch(tiedWith, {deep: true, immediate: true}, fakeHandler); + } } - } - }); + }); + } } // We should not use the getter's cache until the component is fully created. From 2d05e670628c5460024e33579d09e31375d6dfcc Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 29 Oct 2024 13:50:13 +0300 Subject: [PATCH 284/334] fix: fixed initializing of getter dependencies --- src/core/component/accessor/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index d82316b8d6..7e32b10a4a 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -96,7 +96,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { delete tiedFields[name]; const canSkip = - component[name] != null || + name in component || !SSR && isFunctional && accessor.functional === false; if (canSkip) { @@ -121,7 +121,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const deps = watchDependencies.get(name); - if (name !== 'hook' && (deps != null && deps.length > 0 || tiedWith != null)) { + if (deps != null && deps.length > 0 || tiedWith != null) { onCreated(this.hook, () => { // If a computed property has a field or system field as a dependency // and the host component does not have any watchers to this field, @@ -191,7 +191,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { delete tiedFields[name]; const canSkip = - component[name] != null || + name in component || computed.cache === 'auto' || !SSR && isFunctional && computed.functional === false; @@ -221,7 +221,7 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { const deps = watchDependencies.get(name); - if (name !== 'hook' && (deps != null && deps.length > 0 || tiedWith != null)) { + if (deps != null && deps.length > 0 || tiedWith != null) { onCreated(this.hook, () => { // If a computed property has a field or system field as a dependency // and the host component does not have any watchers to this field, @@ -362,8 +362,8 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { // Loopback } - function onCreated(hook: Hook, cb: Function) { - if (beforeHooks[hook] != null) { + function onCreated(hook: Nullable, cb: Function) { + if (hook == null || beforeHooks[hook] != null) { hooks['before:created'].push({fn: cb}); } else { From 981fcea52610c1d671b7a0439b8af7ac26625b56 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 29 Oct 2024 16:41:25 +0300 Subject: [PATCH 285/334] fix: initializing order --- src/core/component/meta/field.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/component/meta/field.ts b/src/core/component/meta/field.ts index 7898092ba8..b8741cbaae 100644 --- a/src/core/component/meta/field.ts +++ b/src/core/component/meta/field.ts @@ -66,7 +66,10 @@ export function sortFields(fields: Dictionary): ComponentFieldIn list.push([name, field]); } - return list.sort(([aName], [bName]) => { + // The for-in loop first iterates over the object's own properties, and then over those from the prototypes, + // which means the initialization order will be reversed. + // To fix this, we need to reverse the list of fields before sorting. + return list.reverse().sort(([aName], [bName]) => { const aWeight = getFieldWeight(fields[aName], fields), bWeight = getFieldWeight(fields[bName], fields); From c5aae9cf9d58b4450c5f5c47bf8cfc44f81cd542 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Tue, 29 Oct 2024 16:45:09 +0300 Subject: [PATCH 286/334] chore: stylish fixes --- src/components/friends/state/router.ts | 14 ++++---------- src/components/friends/state/storage.ts | 8 ++------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/components/friends/state/router.ts b/src/components/friends/state/router.ts index e1ebc8a28f..df3a69f96e 100644 --- a/src/components/friends/state/router.ts +++ b/src/components/friends/state/router.ts @@ -25,10 +25,7 @@ export function initFromRouter(this: State): boolean { return false; } - const { - ctx, - async: $a - } = this; + const {ctx, async: $a} = this; const routerWatchers = {group: 'routerWatchers'}; $a.clearAll(routerWatchers); @@ -37,11 +34,9 @@ export function initFromRouter(this: State): boolean { return true; async function loadFromRouter() { - const - {r} = ctx; + const {r} = ctx; - let - {router} = r; + let {router} = r; if (router == null) { await ($a.promisifyOnce(r, 'initRouter', { @@ -97,8 +92,7 @@ export function initFromRouter(this: State): boolean { if (Object.isDictionary(stateFields)) { Object.keys(stateFields).forEach((key) => { - const - p = key.split('.'); + const p = key.split('.'); if (p[0] === 'mods') { $a.on(ctx.localEmitter, `block.mod.*.${p[1]}.*`, sync, routerWatchers); diff --git a/src/components/friends/state/storage.ts b/src/components/friends/state/storage.ts index 624f58ab3b..d769bd4e33 100644 --- a/src/components/friends/state/storage.ts +++ b/src/components/friends/state/storage.ts @@ -25,17 +25,13 @@ export function initFromStorage(this: Friend): CanPromise { return false; } - const - key = $$.pendingLocalStore; + const key = $$.pendingLocalStore; if (this[key] != null) { return this[key]; } - const { - ctx, - async: $a - } = this; + const {ctx, async: $a} = this; const storeWatchers = {group: 'storeWatchers'}; $a.clearAll(storeWatchers); From a688818ce49f83e9627ae86b5a3314665a1ccd66 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 1 Nov 2024 13:57:38 +0300 Subject: [PATCH 287/334] fix: fixed computed overriding --- src/core/component/decorators/computed/decorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts index 6120411ced..270e0ee28d 100644 --- a/src/core/component/decorators/computed/decorator.ts +++ b/src/core/component/decorators/computed/decorator.ts @@ -90,7 +90,7 @@ export function computed(params?: DecoratorComputed): PartDecorator { }, meta); } - delete meta[cluster === 'computedFields' ? 'accessors' : 'computedFields'][accessorName]; + meta[cluster === 'computedFields' ? 'accessors' : 'computedFields'][accessorName] = undefined; store[accessorName] = accessor; From 1677d0e7e9f7a93f1c88a0d94056dae00fc58ef6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 6 Nov 2024 19:19:12 +0300 Subject: [PATCH 288/334] refactor: optimization from speedup-3-2 --- build/ts-transformers/CHANGELOG.md | 2 +- build/ts-transformers/README.md | 2 +- build/ts-transformers/index.js | 2 +- .../CHANGELOG.md | 0 .../README.md | 17 +- .../register-component-parts/helpers.js | 159 +++++++ .../register-component-parts/index.js | 411 ++++++++++++++++++ .../resister-component-parts/index.js | 357 --------------- src/components/traits/index.ts | 160 +++---- .../component/decorators/component/index.ts | 16 +- src/core/component/decorators/const.ts | 15 +- .../component/decorators/field/decorator.ts | 8 +- src/core/component/decorators/helpers.ts | 20 +- src/core/component/decorators/index.ts | 1 + src/core/component/decorators/interface.ts | 6 + src/core/component/decorators/prop/README.md | 6 - .../component/decorators/prop/decorator.ts | 32 +- .../component/decorators/prop/interface.ts | 9 - .../component/decorators/system/decorator.ts | 40 +- 19 files changed, 767 insertions(+), 496 deletions(-) rename build/ts-transformers/{resister-component-parts => register-component-parts}/CHANGELOG.md (100%) rename build/ts-transformers/{resister-component-parts => register-component-parts}/README.md (67%) create mode 100644 build/ts-transformers/register-component-parts/helpers.js create mode 100644 build/ts-transformers/register-component-parts/index.js delete mode 100644 build/ts-transformers/resister-component-parts/index.js diff --git a/build/ts-transformers/CHANGELOG.md b/build/ts-transformers/CHANGELOG.md index 39ad4c3acc..d596fbde96 100644 --- a/build/ts-transformers/CHANGELOG.md +++ b/build/ts-transformers/CHANGELOG.md @@ -13,7 +13,7 @@ Changelog #### :rocket: New Feature -* Added a new transformer `resister-component-default-values` +* Added a new transformer `register-component-default-values` ## v3.23.5 (2022-07-12) diff --git a/build/ts-transformers/README.md b/build/ts-transformers/README.md index 62b7e5d68c..8a95e4cd67 100644 --- a/build/ts-transformers/README.md +++ b/build/ts-transformers/README.md @@ -7,4 +7,4 @@ This module provides a bunch of custom transformers for TypeScript/TSC. * `set-component-layer` - this module provides a transformer that adds information to each component declaration about the application layer in which the component is declared. -* `resister-component-parts` - this module provides a transformer for registering parts of a class as parts of the associated component. +* `register-component-parts` - this module provides a transformer for registering parts of a class as parts of the associated component. diff --git a/build/ts-transformers/index.js b/build/ts-transformers/index.js index fb40c85839..5eef9935c1 100644 --- a/build/ts-transformers/index.js +++ b/build/ts-transformers/index.js @@ -10,7 +10,7 @@ const setComponentLayer = include('build/ts-transformers/set-component-layer'), - resisterComponentParts = include('build/ts-transformers/resister-component-parts'); + resisterComponentParts = include('build/ts-transformers/register-component-parts'); /** * Returns a settings object for configuring TypeScript transformers diff --git a/build/ts-transformers/resister-component-parts/CHANGELOG.md b/build/ts-transformers/register-component-parts/CHANGELOG.md similarity index 100% rename from build/ts-transformers/resister-component-parts/CHANGELOG.md rename to build/ts-transformers/register-component-parts/CHANGELOG.md diff --git a/build/ts-transformers/resister-component-parts/README.md b/build/ts-transformers/register-component-parts/README.md similarity index 67% rename from build/ts-transformers/resister-component-parts/README.md rename to build/ts-transformers/register-component-parts/README.md index b137eaf2d3..a577049518 100644 --- a/build/ts-transformers/resister-component-parts/README.md +++ b/build/ts-transformers/register-component-parts/README.md @@ -1,4 +1,4 @@ -# build/ts-transformers/resister-component-parts +# build/ts-transformers/register-component-parts This module provides a transformer for registering parts of a class as parts of the associated component. @@ -26,22 +26,23 @@ Will transform to ```typescript import { defaultValue } from 'core/component/decorators/default-value'; -import { method } from 'core/component/decorators/method'; +import { registeredComponent } from 'core/component/decorators/const'; import iBlock, { component, prop } from 'components/super/i-block/i-block'; +registeredComponent.name = 'bExample'; +registeredComponent.layer = '@v4fire/client'; +registeredComponent.event = 'constructor.b-example.@v4fire/client'; + @component() class bExample extends iBlock { - @defaultValue(() => { return []; }) - @prop(Array) + @prop(Array, () => { return []; }) prop: string[] = []; - @method('accessor') get answer() { return 42; } - @method('method') just() { return 'do it'; } @@ -53,10 +54,10 @@ class bExample extends iBlock { To attach the transformer, you need to add its import to `build/ts-transformers`. ```js -const resisterComponentParts = include('build/ts-transformers/resister-component-parts'); +const registerComponentParts = include('build/ts-transformers/register-component-parts'); module.exports = () => ({ - before: [resisterComponentParts], + before: [registerComponentParts], after: {}, afterDeclarations: {} }); diff --git a/build/ts-transformers/register-component-parts/helpers.js b/build/ts-transformers/register-component-parts/helpers.js new file mode 100644 index 0000000000..c1cc051778 --- /dev/null +++ b/build/ts-transformers/register-component-parts/helpers.js @@ -0,0 +1,159 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +'use strict'; + +const ts = require('typescript'); + +/** + * @typedef {import('typescript').TransformationContext} TransformationContext + * @typedef {import('typescript').Node} Node + * @typedef {import('typescript').Decorator} Decorator + */ + +exports.addNamedImport = addNamedImport; + +/** + * Adds an import statement with the specified name to the specified file + * + * @param {string} name - the name of the decorator to be imported and applied (e.g., `defaultValue`) + * @param {string} path - the path from which the decorator should be imported (e.g., `core/component/decorators`) + * @param {TransformationContext} context - the transformation context + * @param {Node} node - the source file node in the AST + * @returns {Node} + */ +function addNamedImport(name, path, context, node) { + const {factory} = context; + + const decoratorSrc = factory.createStringLiteral(path); + + const importSpecifier = factory.createImportSpecifier( + undefined, + undefined, + factory.createIdentifier(name) + ); + + const importClause = factory.createImportClause( + undefined, + undefined, + factory.createNamedImports([importSpecifier]) + ); + + const importDeclaration = factory.createImportDeclaration( + undefined, + undefined, + importClause, + decoratorSrc + ); + + const updatedStatements = factory.createNodeArray([ + importDeclaration, + ...node.statements + ]); + + return factory.updateSourceFile(node, updatedStatements); +} + +exports.isComponentClass = isComponentClass; + +/** + * Returns true if the specified class is a component + * + * @param {Node} node - the class node in the AST + * @returns {boolean} + */ +function isComponentClass(node) { + const {decorators} = node; + + if (!ts.isClassDeclaration(node)) { + return false; + } + + if (decorators != null && decorators.length > 0) { + return decorators.some((node) => isDecorator(node, 'component')); + } + + return false; +} + +exports.getPartialName = getPartialName; + +/** + * Returns the value of the `partial` parameter from the parameters of the @component decorator + * + * @param {Node} node - the class node in the AST + * @returns {boolean} + */ +function getPartialName(node) { + const {decorators} = node; + + if (!ts.isClassDeclaration(node)) { + return false; + } + + if (decorators != null && decorators.length > 0) { + for (const decorator of node.decorators) { + if (isDecorator(decorator, 'component')) { + const args = decorator.expression.arguments; + + if (args.length > 0) { + const params = args[0]; + + if (ts.isObjectLiteralExpression(params)) { + for (const property of params.properties) { + if ( + ts.isPropertyAssignment(property) && + ts.isIdentifier(property.name) && + property.name.text === 'partial' && + ts.isStringLiteral(property.initializer) + ) { + return property.initializer.text; + } + } + } + } + + break; + } + } + } + + return undefined; +} + +const pathToRootRgxp = /(?.+)[/\\]src[/\\]/; + +exports.getLayerName = getLayerName; + +/** + * Takes a file path and returns the package name from the package.json file of the package the provided file belongs to + * + * @param {string} path + * @returns {string} + */ +function getLayerName(path) { + const pathToRootDir = path.match(pathToRootRgxp).groups.path; + return require(`${pathToRootDir}/package.json`).name; +} + +exports.isDecorator = isDecorator; + +/** + * Returns true if the given decorator has the specified name + * + * @param {Decorator} decorator + * @param {string|string[]} name - a name or a list of possible names to match against the decorator. + * @returns {boolean} + */ +function isDecorator(decorator, name) { + return ( + ts.isCallExpression(decorator.expression) && + ts.isIdentifier(decorator.expression.expression) && + Array.toArray(name).includes(decorator.expression.expression.text) + ); +} diff --git a/build/ts-transformers/register-component-parts/index.js b/build/ts-transformers/register-component-parts/index.js new file mode 100644 index 0000000000..e208895c1f --- /dev/null +++ b/build/ts-transformers/register-component-parts/index.js @@ -0,0 +1,411 @@ +/*! + * V4Fire Client Core + * https://github.com/V4Fire/Client + * + * Released under the MIT license + * https://github.com/V4Fire/Client/blob/master/LICENSE + */ + +'use strict'; + +const ts = require('typescript'); + +const { + addNamedImport, + + isDecorator, + isComponentClass, + + getPartialName, + getLayerName +} = include('build/ts-transformers/register-component-parts/helpers'); + +/** + * @typedef {import('typescript').Transformer} Transformer + * @typedef {import('typescript').TransformationContext} TransformationContext + * @typedef {import('typescript').Node} Node + * @typedef {import('typescript').ClassDeclaration} ClassDeclaration + */ + +module.exports = resisterComponentDefaultValues; + +/** + * Registers parts of a class as parts of the associated component. + * For example, all methods and accessors of the class are registered as methods and accessors of the component. + * + * @param {TransformationContext} context + * @returns {Transformer} + * + * @example + * + * ```typescript + * import iBlock, { component, prop } from 'components/super/i-block/i-block'; + * + * @component() + * class bExample extends iBlock { + * @prop(Array) + * prop = []; + * + * get answer() { + * return 42; + * } + * + * just() { + * return 'do it'; + * } + * } + * ``` + * + * Will transform to + * + * ```typescript + * import { defaultValue } from 'core/component/decorators/default-value'; + * import { registeredComponent } from 'core/component/decorators/const'; + * + * import iBlock, { component, prop } from 'components/super/i-block/i-block'; + * + * registeredComponent.name = 'bExample'; + * registeredComponent.layer = '@v4fire/client'; + * registeredComponent.event = 'constructor.b-example.@v4fire/client'; + * + * @component() + * class bExample extends iBlock { + * @prop(Array, () => { return []; }) + * prop: string[] = []; + * + * get answer() { + * return 42; + * } + * + * just() { + * return 'do it'; + * } + * } + * ``` + */ +function resisterComponentDefaultValues(context) { + const {factory} = context; + + let + componentName, + originalComponentName; + + let + needImportMethodDecorator = false, + needImportDefaultValueDecorator = false; + + return (node) => { + node = ts.visitNode(node, visitor); + + if (componentName) { + node = registeredComponentParams( + context, + node, + componentName, + originalComponentName, + getLayerName(node.path) + ); + + node = addNamedImport('registeredComponent', 'core/component/decorators/const', context, node); + } + + if (needImportMethodDecorator) { + node = addNamedImport('method', 'core/component/decorators/method', context, node); + } + + if (needImportDefaultValueDecorator) { + node = addNamedImport('defaultValue', 'core/component/decorators/default-value', context, node); + } + + return node; + }; + + /** + * A visitor for the AST node + * + * @param {Node} node + * @returns {Node|ClassDeclaration} + */ + function visitor(node) { + if (isComponentClass(node, 'component')) { + originalComponentName = node.name.text; + componentName = getPartialName(node) ?? originalComponentName; + + if (node.members != null) { + const newMembers = node.members.flatMap((node) => { + if ( + ts.isPropertyDeclaration(node) && + ts.hasInitializer(node) && + !node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword) + ) { + needImportDefaultValueDecorator = true; + return addDefaultValueDecorator(context, node); + } + + const + isGetter = ts.isGetAccessorDeclaration(node), + isSetter = !isGetter && ts.isSetAccessorDeclaration(node); + + if (isGetter || isSetter || ts.isMethodDeclaration(node)) { + needImportMethodDecorator = true; + node = addMethodDecorator(context, node); + } + + if (isGetter || ts.isSetAccessorDeclaration(node)) { + const + postfix = isGetter ? 'Getter' : 'Setter', + methodName = node.name.text + postfix; + + const method = factory.createMethodDeclaration( + undefined, + undefined, + undefined, + context.factory.createStringLiteral(methodName), + undefined, + undefined, + node.parameters, + undefined, + node.body + ); + + return [node, method]; + } + + return node; + }); + + return factory.updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + newMembers + ); + } + } + + return ts.visitEachChild(node, visitor, context); + } +} + +/** + * Registers the component parameters for initializing the DSL + * + * @param {TransformationContext} context - the transformation context + * @param {Node} node - the node representing the component class + * @param {string} componentName - the name of the component being targeted for registration + * @param {string} originalComponentName - the original name of the component for registration + * @param {string} layerName - the name of the layer in which the component is registered + * @returns {Node} + */ +function registeredComponentParams( + context, + node, + componentName, + originalComponentName, + layerName +) { + const statements = []; + + const {factory} = ts; + + node.statements.forEach((node) => { + if (isComponentClass(node, 'component') && node.name.text === originalComponentName) { + statements.push( + register('name', componentName), + register('name', componentName), + register('layer', layerName), + register('event', `constructor.${componentName.dasherize()}.${layerName}`), + node + ); + + } else { + statements.push(node); + } + }); + + return factory.updateSourceFile(node, factory.createNodeArray(statements)); + + function register(name, value) { + const exprValue = Object.isString(value) ? + factory.createStringLiteral(value) : + factory.createArrayLiteralExpression([...value].map((value) => factory.createStringLiteral(value))); + + return factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('registeredComponent'), + factory.createIdentifier(name) + ), + + factory.createToken(ts.SyntaxKind.EqualsToken), + exprValue + ) + ); + } +} + +/** + * Adds the @defaultValue decorator for the specified class property + * + * @param {TransformationContext} context - the transformation context + * @param {Node} node - the property node in the AST + * @returns {Node} + */ +function addDefaultValueDecorator(context, node) { + const {factory} = context; + + const defaultValue = ts.getEffectiveInitializer(node); + + let getter; + + if ( + ts.isNumericLiteral(defaultValue) || + ts.isBigIntLiteral(defaultValue) || + ts.isStringLiteral(defaultValue) || + defaultValue.kind === ts.SyntaxKind.UndefinedKeyword || + defaultValue.kind === ts.SyntaxKind.NullKeyword || + defaultValue.kind === ts.SyntaxKind.TrueKeyword || + defaultValue.kind === ts.SyntaxKind.FalseKeyword + ) { + getter = defaultValue; + + } else { + const getterValue = factory.createBlock( + [factory.createReturnStatement(defaultValue)], + true + ); + + getter = factory.createFunctionExpression( + undefined, + undefined, + 'getter', + undefined, + [], + undefined, + getterValue + ); + } + + let hasOwnDecorator = false; + + if (node.decorators != null) { + node.decorators = node.decorators.map((node) => { + if (isDecorator(node, ['prop', 'field', 'system'])) { + hasOwnDecorator = true; + + const expr = node.expression; + + const decoratorExpr = factory.createCallExpression( + expr.expression, + undefined, + [expr.arguments[0] ?? factory.createIdentifier('undefined'), getter] + ); + + return factory.updateDecorator(node, decoratorExpr); + } + + return node; + }); + } + + if (!hasOwnDecorator) { + const decoratorExpr = factory.createCallExpression( + factory.createIdentifier('defaultValue'), + undefined, + [getter] + ); + + const decorator = factory.createDecorator(decoratorExpr); + + const decorators = factory.createNodeArray([decorator, ...(node.decorators || [])]); + + return factory.updatePropertyDeclaration( + node, + decorators, + node.modifiers, + node.name, + node.questionToken, + node.type, + node.initializer + ); + } + + return factory.updatePropertyDeclaration( + node, + factory.createNodeArray(node.decorators), + node.modifiers, + node.name, + node.questionToken, + node.type, + node.initializer + ); +} + +/** + * Adds the `@method` decorator for the specified class method or accessor + * + * @param {TransformationContext} context - the transformation context + * @param {Node} node - the method/accessor node in the AST + * @returns {Node} + */ +function addMethodDecorator(context, node) { + const {factory} = context; + + let type; + + if (ts.isMethodDeclaration(node)) { + type = 'method'; + + } else { + type = 'accessor'; + } + + const decoratorExpr = factory.createCallExpression( + factory.createIdentifier('method'), + undefined, + [factory.createStringLiteral(type)] + ); + + const decorator = factory.createDecorator(decoratorExpr); + + const decorators = factory.createNodeArray([decorator, ...(node.decorators || [])]); + + if (ts.isMethodDeclaration(node)) { + return factory.updateMethodDeclaration( + node, + decorators, + node.modifiers, + node.asteriskToken, + node.name, + node.questionToken, + node.typeParameters, + node.parameters, + node.type, + node.body + ); + } + + if (ts.isGetAccessorDeclaration(node)) { + return factory.updateGetAccessorDeclaration( + node, + decorators, + node.modifiers, + node.name, + node.parameters, + node.type, + node.body + ); + } + + return factory.updateSetAccessorDeclaration( + node, + decorators, + node.modifiers, + node.name, + node.parameters, + node.body + ); +} diff --git a/build/ts-transformers/resister-component-parts/index.js b/build/ts-transformers/resister-component-parts/index.js deleted file mode 100644 index 15fff00d83..0000000000 --- a/build/ts-transformers/resister-component-parts/index.js +++ /dev/null @@ -1,357 +0,0 @@ -/*! - * V4Fire Client Core - * https://github.com/V4Fire/Client - * - * Released under the MIT license - * https://github.com/V4Fire/Client/blob/master/LICENSE - */ - -'use strict'; - -const ts = require('typescript'); - -/** - * @typedef {import('typescript').Transformer} Transformer - * @typedef {import('typescript').TransformationContext} TransformationContext - * @typedef {import('typescript').Node} Node - * @typedef {import('typescript').ClassDeclaration} ClassDeclaration - */ - -module.exports = resisterComponentDefaultValues; - -/** - * Registers parts of a class as parts of the associated component. - * For example, all methods and accessors of the class are registered as methods and accessors of the component. - * - * @param {TransformationContext} context - * @returns {Transformer} - * - * @example - * - * ```typescript - * import iBlock, { component, prop } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @prop(Array) - * prop = []; - * - * get answer() { - * return 42; - * } - * - * just() { - * return 'do it'; - * } - * } - * ``` - * - * Will transform to - * - * ```typescript - * import { defaultValue } from 'core/component/decorators/default-value'; - * import { method } from 'core/component/decorators/method'; - * - * import iBlock, { component, prop } from 'components/super/i-block/i-block'; - * - * @component() - * class bExample extends iBlock { - * @defaultValue(() => []) - * @prop(Array) - * prop = []; - * - * @method('accessor') - * get answer() { - * return 42; - * } - * - * @method('method') - * just() { - * return 'do it'; - * } - * } - * ``` - */ -function resisterComponentDefaultValues(context) { - const {factory} = context; - - let - needImportDefaultValueDecorator = false, - needImportMethodDecorator = false; - - return (node) => { - node = ts.visitNode(node, visitor); - - if (needImportDefaultValueDecorator) { - node = addDecoratorImport('defaultValue', 'core/component/decorators/default-value', context, node); - } - - if (needImportMethodDecorator) { - node = addDecoratorImport('method', 'core/component/decorators/method', context, node); - } - - return node; - }; - - /** - * A visitor for the AST node - * - * @param {Node} node - * @returns {Node|ClassDeclaration} - */ - function visitor(node) { - if (isComponentClass(node, 'component') && node.members != null) { - const newMembers = node.members.flatMap((node) => { - if ( - ts.isPropertyDeclaration(node) && - ts.hasInitializer(node) && - !node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword) - ) { - needImportDefaultValueDecorator = true; - return addDefaultValueDecorator(context, node); - } - - const name = node.name.getText(); - - const - isGetter = ts.isGetAccessorDeclaration(node), - isSetter = !isGetter && ts.isSetAccessorDeclaration(node); - - if (isGetter || isSetter || ts.isMethodDeclaration(node)) { - needImportMethodDecorator = true; - node = addMethodDecorator(context, node); - } - - if (isGetter || isSetter) { - const - postfix = isGetter ? 'Getter' : 'Setter', - methodName = context.factory.createStringLiteral(name + postfix); - - const method = factory.createMethodDeclaration( - undefined, - undefined, - undefined, - methodName, - undefined, - undefined, - node.parameters, - undefined, - node.body - ); - - return [node, method]; - } - - return node; - }); - - return factory.updateClassDeclaration( - node, - node.decorators, - node.modifiers, - node.name, - node.typeParameters, - node.heritageClauses, - newMembers - ); - } - - return ts.visitEachChild(node, visitor, context); - } -} - -/** - * Adds the @defaultValue decorator for the specified class property - * - * @param {TransformationContext} context - the transformation context - * @param {Node} node - the property node in the AST - * @returns {Node} - */ -function addDefaultValueDecorator(context, node) { - const {factory} = context; - - const defaultValue = ts.getEffectiveInitializer(node); - - let getter; - - if ( - ts.isNumericLiteral(defaultValue) || - ts.isBigIntLiteral(defaultValue) || - ts.isStringLiteral(defaultValue) || - defaultValue.kind === ts.SyntaxKind.UndefinedKeyword || - defaultValue.kind === ts.SyntaxKind.NullKeyword || - defaultValue.kind === ts.SyntaxKind.TrueKeyword || - defaultValue.kind === ts.SyntaxKind.FalseKeyword - ) { - getter = defaultValue; - - } else { - const getterValue = factory.createBlock( - [factory.createReturnStatement(defaultValue)], - true - ); - - getter = factory.createFunctionExpression( - undefined, - undefined, - 'getter', - undefined, - [], - undefined, - getterValue - ); - } - - const decoratorExpr = factory.createCallExpression( - factory.createIdentifier('defaultValue'), - undefined, - [getter] - ); - - const decorator = factory.createDecorator(decoratorExpr); - - const decorators = factory.createNodeArray([decorator, ...(node.decorators || [])]); - - return factory.updatePropertyDeclaration( - node, - decorators, - node.modifiers, - node.name, - node.questionToken, - node.type, - node.initializer - ); -} - -/** - * Adds the @method decorator for the specified class method or accessor - * - * @param {TransformationContext} context - the transformation context - * @param {Node} node - the method/accessor node in the AST - * @returns {Node} - */ -function addMethodDecorator(context, node) { - const {factory} = context; - - let type; - - if (ts.isMethodDeclaration(node)) { - type = 'method'; - - } else { - type = 'accessor'; - } - - const decoratorExpr = factory.createCallExpression( - factory.createIdentifier('method'), - undefined, - [factory.createStringLiteral(type)] - ); - - const decorator = factory.createDecorator(decoratorExpr); - - const decorators = factory.createNodeArray([decorator, ...(node.decorators || [])]); - - if (ts.isMethodDeclaration(node)) { - return factory.updateMethodDeclaration( - node, - decorators, - node.modifiers, - node.asteriskToken, - node.name, - node.questionToken, - node.typeParameters, - node.parameters, - node.type, - node.body - ); - } - - if (ts.isGetAccessorDeclaration(node)) { - return factory.updateGetAccessorDeclaration( - node, - decorators, - node.modifiers, - node.name, - node.parameters, - node.type, - node.body - ); - } - - return factory.updateSetAccessorDeclaration( - node, - decorators, - node.modifiers, - node.name, - node.parameters, - node.body - ); -} - -/** - * Adds the import for the decorator with the specified name to the specified file - * - * @param {string} name - the name of the decorator to be imported and applied (e.g., `defaultValue`) - * @param {string} path - the path from which the decorator should be imported (e.g., `core/component/decorators`) - * @param {TransformationContext} context - the transformation context - * @param {Node} node - the source file node in the AST - * @returns {Node} - */ -function addDecoratorImport(name, path, context, node) { - const {factory} = context; - - const decoratorSrc = factory.createStringLiteral(path); - - const importSpecifier = factory.createImportSpecifier( - undefined, - undefined, - factory.createIdentifier(name) - ); - - const importClause = factory.createImportClause( - undefined, - undefined, - factory.createNamedImports([importSpecifier]) - ); - - const importDeclaration = factory.createImportDeclaration( - undefined, - undefined, - importClause, - decoratorSrc - ); - - const updatedStatements = factory.createNodeArray([ - importDeclaration, - ...node.statements - ]); - - return factory.updateSourceFile(node, updatedStatements); -} - -/** - * Returns true if the specified class is a component - * - * @param {Node} node - the class node in the AST - * @returns {boolean} - */ -function isComponentClass(node) { - const {decorators} = node; - - if (!ts.isClassDeclaration(node)) { - return false; - } - - const getDecoratorName = (decorator) => ( - decorator.expression && - decorator.expression.expression && - ts.getEscapedTextOfIdentifierOrLiteral(decorator.expression.expression) - ); - - if (decorators != null && decorators.length > 0) { - return decorators.some((item) => getDecoratorName(item) === 'component'); - } - - return false; -} diff --git a/src/components/traits/index.ts b/src/components/traits/index.ts index b18b76d1c3..a0b4e88e4b 100644 --- a/src/components/traits/index.ts +++ b/src/components/traits/index.ts @@ -12,6 +12,8 @@ */ import { initEmitter, ComponentDescriptor } from 'core/component'; + +import { registeredComponent } from 'core/component/decorators'; import { regMethod, MethodType } from 'core/component/decorators/method'; /** @@ -67,89 +69,91 @@ import { regMethod, MethodType } from 'core/component/decorators/method'; */ export function derive(...traits: Function[]) { return (target: Function): void => { - initEmitter.once('bindConstructor', (_: string, regEvent: string) => { - initEmitter.once(regEvent, ({meta}: ComponentDescriptor) => { - const proto = target.prototype; - - for (let i = 0; i < traits.length; i++) { - const - originalTrait = traits[i], - chain = getTraitChain(originalTrait); - - for (let i = 0; i < chain.length; i++) { - const [trait, keys] = chain[i]; - - for (let i = 0; i < keys.length; i++) { - const - key = keys[i], - defMethod = Object.getOwnPropertyDescriptor(trait, key), - traitMethod = Object.getOwnPropertyDescriptor(trait.prototype, key); - - const canDerive = - defMethod != null && - traitMethod != null && - !(key in proto) && - - Object.isFunction(defMethod.value) && ( - Object.isFunction(traitMethod.value) || - - // eslint-disable-next-line @v4fire/unbound-method - Object.isFunction(traitMethod.get) || Object.isFunction(traitMethod.set) - ); - - if (canDerive) { - let type: MethodType; - - const newDescriptor: PropertyDescriptor = { - enumerable: false, - configurable: true - }; - - if (Object.isFunction(traitMethod.value)) { - Object.assign(newDescriptor, { - writable: true, - - // eslint-disable-next-line func-name-matching - value: function defaultMethod(...args: unknown[]) { - return originalTrait[key](this, ...args); - } - }); - - type = 'method'; - - } else { - Object.assign(newDescriptor, { - get() { - return originalTrait[key](this); - }, - - set(value: unknown) { - originalTrait[key](this, value); - } - }); - - type = 'accessor'; - } - - Object.defineProperty(proto, key, newDescriptor); - regMethod(key, type, meta, proto); + if (registeredComponent.event == null) { + return; + } + + initEmitter.once(registeredComponent.event, ({meta}: ComponentDescriptor) => { + const proto = target.prototype; + + for (let i = 0; i < traits.length; i++) { + const + originalTrait = traits[i], + chain = getTraitChain(originalTrait); + + for (let i = 0; i < chain.length; i++) { + const [trait, keys] = chain[i]; + + for (let i = 0; i < keys.length; i++) { + const + key = keys[i], + defMethod = Object.getOwnPropertyDescriptor(trait, key), + traitMethod = Object.getOwnPropertyDescriptor(trait.prototype, key); + + const canDerive = + defMethod != null && + traitMethod != null && + !(key in proto) && + + Object.isFunction(defMethod.value) && ( + Object.isFunction(traitMethod.value) || + + // eslint-disable-next-line @v4fire/unbound-method + Object.isFunction(traitMethod.get) || Object.isFunction(traitMethod.set) + ); + + if (canDerive) { + let type: MethodType; + + const newDescriptor: PropertyDescriptor = { + enumerable: false, + configurable: true + }; + + if (Object.isFunction(traitMethod.value)) { + Object.assign(newDescriptor, { + writable: true, + + // eslint-disable-next-line func-name-matching + value: function defaultMethod(...args: unknown[]) { + return originalTrait[key](this, ...args); + } + }); + + type = 'method'; + + } else { + Object.assign(newDescriptor, { + get() { + return originalTrait[key](this); + }, + + set(value: unknown) { + originalTrait[key](this, value); + } + }); + + type = 'accessor'; } + + Object.defineProperty(proto, key, newDescriptor); + regMethod(key, type, meta, proto); } } } - - function getTraitChain>( - trait: Nullable, - methods: T = Object.cast([]) - ): T { - if (!Object.isFunction(trait) || trait === Function.prototype) { - return methods; - } - - methods.push([trait, Object.getOwnPropertyNames(trait)]); - return getTraitChain(Object.getPrototypeOf(trait), methods); + } + + function getTraitChain>( + trait: Nullable, + methods: T = Object.cast([]) + ): T { + if (!Object.isFunction(trait) || trait === Function.prototype) { + return methods; } - }); + + methods.push([trait, Object.getOwnPropertyNames(trait)]); + return getTraitChain(Object.getPrototypeOf(trait), methods); + } }); }; } diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index a0167acdd3..c73106f8a1 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -42,6 +42,8 @@ import { getComponent, ComponentEngine } from 'core/component/engines'; import { getComponentMods, getInfoFromConstructor } from 'core/component/reflect'; import { registerComponent, registerParentComponents } from 'core/component/init'; +import { registeredComponent } from 'core/component/decorators/const'; + import type { ComponentConstructor, ComponentOptions } from 'core/component/interface'; const OVERRIDDEN = Symbol('This class is overridden in the child layer'); @@ -73,6 +75,12 @@ const OVERRIDDEN = Symbol('This class is overridden in the child layer'); */ export function component(opts?: ComponentOptions): Function { return (target: ComponentConstructor) => { + if (registeredComponent.event == null) { + return; + } + + const regComponentEvent = registeredComponent.event; + const componentInfo = getInfoFromConstructor(target, opts), componentParams = componentInfo.params, @@ -87,12 +95,6 @@ export function component(opts?: ComponentOptions): Function { Object.defineProperty(componentInfo.parent, OVERRIDDEN, {value: true}); } - // Add information about the layer in which the component is described - // to correctly handle situations where the component is overridden in child layers of the application - const regEvent = `constructor.${componentNormalizedName}.${componentInfo.layer}`; - - initEmitter.emit('bindConstructor', componentNormalizedName, regEvent); - if (isPartial) { pushToInitList(() => { // Partial classes reuse the same metaobject @@ -207,7 +209,7 @@ export function component(opts?: ComponentOptions): Function { components.set(target, meta); } - initEmitter.emit(regEvent, { + initEmitter.emit(regComponentEvent, { meta, parentMeta: componentInfo.parentMeta }); diff --git a/src/core/component/decorators/const.ts b/src/core/component/decorators/const.ts index b297c7844f..bd7b2f2655 100644 --- a/src/core/component/decorators/const.ts +++ b/src/core/component/decorators/const.ts @@ -6,13 +6,10 @@ * https://github.com/V4Fire/Client/blob/master/LICENSE */ -export const invertedFieldMap = Object.createDict({ - props: ['fields', 'systemFields'], - fields: ['props', 'systemFields'], - systemFields: ['props', 'fields'] -}); +import type { RegisteredComponent } from 'core/component/decorators/interface'; -export const tiedFieldMap = Object.createDict({ - fields: true, - systemFields: true -}); +export const registeredComponent: RegisteredComponent = { + name: undefined, + layer: undefined, + event: undefined +}; diff --git a/src/core/component/decorators/field/decorator.ts b/src/core/component/decorators/field/decorator.ts index d33a126513..699935db4d 100644 --- a/src/core/component/decorators/field/decorator.ts +++ b/src/core/component/decorators/field/decorator.ts @@ -17,6 +17,7 @@ import type { InitFieldFn, DecoratorField } from 'core/component/decorators/fiel * * @decorator * @param [initOrParams] - a function to initialize the field value or an object with field parameters + * @param [initOrDefault] - a function to initialize the field value or the field default value * * @example * ```typescript @@ -32,6 +33,9 @@ import type { InitFieldFn, DecoratorField } from 'core/component/decorators/fiel * } * ``` */ -export function field(initOrParams?: InitFieldFn | DecoratorField): PartDecorator { - return system(initOrParams, 'fields'); +export function field( + initOrParams?: InitFieldFn | DecoratorField, + initOrDefault?: InitFieldFn | DecoratorField['default'] +): PartDecorator { + return system(initOrParams, initOrDefault, 'fields'); } diff --git a/src/core/component/decorators/helpers.ts b/src/core/component/decorators/helpers.ts index 92b0b0ea08..2621219059 100644 --- a/src/core/component/decorators/helpers.ts +++ b/src/core/component/decorators/helpers.ts @@ -10,6 +10,8 @@ import { initEmitter } from 'core/component/event'; import type { ComponentMeta } from 'core/component/meta'; +import { registeredComponent } from 'core/component/decorators/const'; + import type { PartDecorator, @@ -52,15 +54,17 @@ function createComponentDecorator( partDesc: CanUndef, proto: object ): void { - initEmitter.once('bindConstructor', (_componentName: string, regEvent: string) => { - initEmitter.once(regEvent, (componentDesc: ComponentDescriptor) => { - if (decorator.length <= 3) { - (decorator)(componentDesc, partKey, proto); + if (registeredComponent.event == null) { + return; + } + + initEmitter.once(registeredComponent.event, (componentDesc: ComponentDescriptor) => { + if (decorator.length <= 3) { + (decorator)(componentDesc, partKey, proto); - } else { - (decorator)(componentDesc, partKey, partDesc, proto); - } - }); + } else { + (decorator)(componentDesc, partKey, partDesc, proto); + } }); } diff --git a/src/core/component/decorators/index.ts b/src/core/component/decorators/index.ts index fff0b5a014..bd4395b37e 100644 --- a/src/core/component/decorators/index.ts +++ b/src/core/component/decorators/index.ts @@ -21,4 +21,5 @@ export * from 'core/component/decorators/computed'; export * from 'core/component/decorators/hook'; export * from 'core/component/decorators/watch'; +export * from 'core/component/decorators/const'; export * from 'core/component/decorators/interface'; diff --git a/src/core/component/decorators/interface.ts b/src/core/component/decorators/interface.ts index ef8715f9be..f1b803b244 100644 --- a/src/core/component/decorators/interface.ts +++ b/src/core/component/decorators/interface.ts @@ -32,3 +32,9 @@ export interface ComponentPartDecorator4 { export interface PartDecorator { (target: object, partKey: string, partDesc?: PropertyDescriptor): void; } + +export interface RegisteredComponent { + name?: string; + layer?: string; + event?: string; +} diff --git a/src/core/component/decorators/prop/README.md b/src/core/component/decorators/prop/README.md index ebcd2f46ae..5ecaac26bc 100644 --- a/src/core/component/decorators/prop/README.md +++ b/src/core/component/decorators/prop/README.md @@ -392,9 +392,3 @@ class bExample extends iBlock { ### [functional = `true`] If set to false, the prop can't be passed to a functional component. - -### [forceDefault = `false`] - -If set to true, the prop always uses its own default value when needed. -This option is actually used when the `defaultProps` property is set to false for the described component -(via the `@component` decorator) and we want to override this behavior for a particular prop. diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 922d0c8542..02792d4f16 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -22,6 +22,7 @@ import type { DecoratorProp, PropType } from 'core/component/decorators/prop/int * * @decorator * @param [typeOrParams] - a constructor of the prop type or an object with prop parameters + * @param [defaultValue] - default prop value * * @example * ```typescript @@ -40,9 +41,29 @@ import type { DecoratorProp, PropType } from 'core/component/decorators/prop/int * } * ``` */ -export function prop(typeOrParams?: PropType | DecoratorProp): PartDecorator { +export function prop( + typeOrParams?: PropType | DecoratorProp, + defaultValue?: DecoratorProp['default'] +): PartDecorator { return createComponentDecorator3((desc, propName) => { - regProp(propName, typeOrParams, desc.meta); + const hasDefault = Object.isDictionary(typeOrParams) && 'default' in typeOrParams; + + let params = typeOrParams; + + if (defaultValue !== undefined && !hasDefault) { + if (Object.isDictionary(params)) { + params.default = defaultValue; + + } else { + params = {default: defaultValue}; + + if (typeOrParams !== undefined) { + params.type = typeOrParams; + } + } + } + + regProp(propName, params, desc.meta); }); } @@ -173,10 +194,9 @@ export function regProp(propName: string, typeOrParams: Nullable, cluster: 'fields'): PartDecorator; +export function system( + initOrParams: CanUndef, + initOrDefault: InitFieldFn | DecoratorSystem['default'], + cluster: 'fields' +): PartDecorator; export function system( initOrParams?: InitFieldFn | DecoratorSystem | DecoratorField, + initOrDefault?: InitFieldFn | DecoratorSystem['default'], cluster: FieldCluster = 'systemFields' ): PartDecorator { return createComponentDecorator3((desc, fieldName) => { - regField(fieldName, cluster, initOrParams, desc.meta); + const hasInitOrDefault = + Object.isFunction(initOrParams) || + Object.isDictionary(initOrParams) && ('init' in initOrParams || 'default' in initOrParams); + + let params = initOrParams; + + if (initOrDefault !== undefined && !hasInitOrDefault) { + if (Object.isFunction(initOrDefault)) { + if (Object.isDictionary(params)) { + params.init = initOrDefault; + + } else { + params = initOrDefault; + } + + } else if (Object.isDictionary(params)) { + params.default = initOrDefault; + + } else { + params = {default: initOrDefault}; + } + } + + regField(fieldName, cluster, params, desc.meta); }); } From b2ac6994e30502e6969a2f59842e16736bbed94b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 6 Nov 2024 19:30:42 +0300 Subject: [PATCH 289/334] chore: fixed test --- src/components/base/b-dynamic-page/test/unit/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/base/b-dynamic-page/test/unit/main.ts b/src/components/base/b-dynamic-page/test/unit/main.ts index b51ca3d184..93234aa3fd 100644 --- a/src/components/base/b-dynamic-page/test/unit/main.ts +++ b/src/components/base/b-dynamic-page/test/unit/main.ts @@ -95,7 +95,7 @@ test.describe('', () => { await test.expect( target.evaluate((ctx) => { const {meta} = ctx.unsafe; - return 'component' in meta.accessors && !('component' in meta.computedFields); + return meta.accessors.component != null && meta.computedFields.component == null; }) ).toBeResolvedTo(true); }); From 7231e88819ebef91cc4b52bba7e4d8f26dcdaeb8 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 6 Nov 2024 19:43:30 +0300 Subject: [PATCH 290/334] chore: updated example --- build/ts-transformers/register-component-parts/README.md | 3 +++ build/ts-transformers/register-component-parts/index.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/build/ts-transformers/register-component-parts/README.md b/build/ts-transformers/register-component-parts/README.md index a577049518..bb2d7a0b03 100644 --- a/build/ts-transformers/register-component-parts/README.md +++ b/build/ts-transformers/register-component-parts/README.md @@ -25,6 +25,7 @@ class bExample extends iBlock { Will transform to ```typescript +import { method } from 'core/component/decorators/method'; import { defaultValue } from 'core/component/decorators/default-value'; import { registeredComponent } from 'core/component/decorators/const'; @@ -39,10 +40,12 @@ class bExample extends iBlock { @prop(Array, () => { return []; }) prop: string[] = []; + @method('accessor') get answer() { return 42; } + @method('method') just() { return 'do it'; } diff --git a/build/ts-transformers/register-component-parts/index.js b/build/ts-transformers/register-component-parts/index.js index e208895c1f..8d7777fb89 100644 --- a/build/ts-transformers/register-component-parts/index.js +++ b/build/ts-transformers/register-component-parts/index.js @@ -59,6 +59,7 @@ module.exports = resisterComponentDefaultValues; * Will transform to * * ```typescript + * import { method } from 'core/component/decorators/method'; * import { defaultValue } from 'core/component/decorators/default-value'; * import { registeredComponent } from 'core/component/decorators/const'; * @@ -73,10 +74,12 @@ module.exports = resisterComponentDefaultValues; * @prop(Array, () => { return []; }) * prop: string[] = []; * + * @method('accessor') * get answer() { * return 42; * } * + * @method('method') * just() { * return 'do it'; * } From 3fef7cf2dd9e8cba421780cac10cde445783d2ec Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 14:00:51 +0300 Subject: [PATCH 291/334] refactor: removed `forceDefault` & `defaultProps`; now `getPassedProps` returns a dictionary; removed getPassedHandlers; fixed b-dynamic-page --- .../base/b-dynamic-page/b-dynamic-page.ss | 19 +--- .../base/b-dynamic-page/b-dynamic-page.ts | 86 +++++++++++-------- .../base/b-prevent-ssr/b-prevent-ssr.ts | 3 +- .../b-remote-provider/b-remote-provider.ts | 16 ++-- src/components/base/b-tree/b-tree.ts | 4 +- .../super/i-block/modules/mods/index.ts | 14 +-- src/components/super/i-block/props.ts | 9 +- .../component/decorators/component/README.md | 5 -- .../component/decorators/method/decorator.ts | 4 +- src/core/component/event/component.ts | 4 +- .../component/functional/context/inherit.ts | 14 +-- .../interface/component/component.ts | 9 +- src/core/component/meta/interface/options.ts | 8 -- src/core/component/meta/interface/types.ts | 1 - src/core/component/prop/helpers.ts | 12 ++- src/core/component/prop/init.ts | 8 +- src/core/component/render/wrappers.ts | 27 +----- src/core/component/watch/helpers.ts | 2 +- 18 files changed, 104 insertions(+), 141 deletions(-) diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ss b/src/components/base/b-dynamic-page/b-dynamic-page.ss index 5bf2fe35d3..0754bd6521 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ss +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ss @@ -17,21 +17,6 @@ ? rootAttrs['v-memo'] = '[]' - block body - : graph = include('build/graph/component-params') - - ? Object.assign(attrs, graph.getComponentPropAttrs(self.name(PARENT_TPL_NAME))) - - ? delete attrs[':is'] - ? delete attrs[':keepAlive'] - ? delete attrs[':dispatching'] - - ? delete attrs[':componentId'] - ? delete attrs[':getRoot'] - ? delete attrs[':getParent'] - - ? delete attrs[':getPassedHandlers'] - ? delete attrs[':getPassedProps'] - < template v-for = el in asyncRender.iterate(renderIterator, {filter: renderFilter, group: registerRenderGroup}) < component.&__component & v-if = !pageTakenFromCache && page != null | @@ -39,9 +24,7 @@ :is = page | :instanceOf = iDynamicPage | - :dispatching = true | :canFunctional = false | - v-attrs = {'@hook:destroyed': createPageDestructor()} | - ${attrs} + v-attrs = getPageProps() . diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index ab157eae6f..d73d867ad3 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -64,18 +64,10 @@ export * from 'components/base/b-dynamic-page/interface'; Block.addToPrototype({element}); AsyncRender.addToPrototype({iterate}); -const - $$ = symbolGenerator(); - -@component({ - inheritMods: false, - defaultProps: false -}) +const $$ = symbolGenerator(); +@component({inheritMods: false}) export default class bDynamicPage extends iDynamicPage { - @prop({forceDefault: true}) - override readonly selfDispatching: boolean = true; - /** * The initial name of the page to load */ @@ -103,23 +95,23 @@ export default class bDynamicPage extends iDynamicPage { page?: string; /** - * Active page unique key. - * It is used to determine whether to reuse current page component or create a new one when switching between routes - * with the same page component. + * The active page unique key. + * It is used to determine whether to reuse the current page component + * or create a new one when switching between routes with the same page component. */ @system() pageKey?: CanUndef; /** - * A function that takes a route object and returns the name of the page component to load. - * Also, this function can return a tuple consisting of component name and unique key for the passed route. The key - * will be used to determine whether to reuse current page component or create a new one - * when switching between routes with the same page component. + * A function that takes a route object and returns the name of the page component to be loaded. + * Additionally, this function can return a tuple consisting of the component name and a unique key + * for the given route. + * The key will be used to determine whether to reuse the current page component + * or create a new one when switching between routes that use the same page component. */ @prop({ type: Function, - default: (route: bDynamicPage['route']) => route != null ? (route.meta.component ?? route.name) : undefined, - forceDefault: true + default: (route: bDynamicPage['route']) => route != null ? (route.meta.component ?? route.name) : undefined }) readonly pageGetter!: PageGetter; @@ -198,12 +190,7 @@ export default class bDynamicPage extends iDynamicPage { /** * The page switching event name */ - @prop({ - type: String, - required: false, - forceDefault: true - }) - + @prop({type: String, required: false}) readonly event?: string = 'setRoute'; /** @@ -326,8 +313,40 @@ export default class bDynamicPage extends iDynamicPage { return component.reload(params); } - override canSelfDispatchEvent(event: string): boolean { - return !/^hook(?::\w+(-\w+)*|-change)$/.test(event.dasherize()); + /** + * Returns a dictionary of props for the page being created. + * The component interprets most of its input props as parameters for the page being created. + */ + protected getPageProps(): Dictionary { + const + props = {'@hook:destroyed': this.createPageDestructor()}, + passedProps = this.getPassedProps?.(); + + if (passedProps != null) { + const rejectedProps = { + is: true, + keepAlive: true, + dispatching: true, + componentIdProp: true, + getRoot: true, + getParent: true, + getPassedProps: true + }; + + Object.entries(passedProps).forEach(([propName, prop]) => { + if (rejectedProps.hasOwnProperty(propName)) { + return; + } + + if (propName.startsWith('on')) { + propName = `@${propName[2].toLowerCase()}${propName.slice(3)}`; + } + + props[propName] = prop; + }); + } + + return props; } /** @@ -535,14 +554,6 @@ export default class bDynamicPage extends iDynamicPage { return globalStrategy; } - protected override initBaseAPI(): void { - super.initBaseAPI(); - - const i = (this.constructor).prototype; - - this.addClearListenersToCache = i.addClearListenersToCache.bind(this); - } - /** * Wraps the specified cache object and returns a wrapper. * The method adds listeners to destroy unused pages from the cache. @@ -664,6 +675,11 @@ export default class bDynamicPage extends iDynamicPage { } } + protected override initBaseAPI(): void { + super.initBaseAPI(); + this.addClearListenersToCache = this.instance.addClearListenersToCache.bind(this); + } + protected override initModEvents(): void { super.initModEvents(); diff --git a/src/components/base/b-prevent-ssr/b-prevent-ssr.ts b/src/components/base/b-prevent-ssr/b-prevent-ssr.ts index 2d0abfe9e3..9977fda50b 100644 --- a/src/components/base/b-prevent-ssr/b-prevent-ssr.ts +++ b/src/components/base/b-prevent-ssr/b-prevent-ssr.ts @@ -11,12 +11,11 @@ * @packageDocumentation */ -import iBlock, { component, prop } from 'components/super/i-block/i-block'; +import iBlock, { component } from 'components/super/i-block/i-block'; export * from 'components/super/i-block/i-block'; @component() export default class bPreventSSR extends iBlock { - @prop({forceDefault: true}) override readonly ssrRenderingProp: boolean = false; } diff --git a/src/components/base/b-remote-provider/b-remote-provider.ts b/src/components/base/b-remote-provider/b-remote-provider.ts index 9e3d8d617c..c7f1842f3b 100644 --- a/src/components/base/b-remote-provider/b-remote-provider.ts +++ b/src/components/base/b-remote-provider/b-remote-provider.ts @@ -96,9 +96,9 @@ export default class bRemoteProvider extends iData { * @emits `error(err:Error |` [[RequestError]]`, retry:` [[RetryRequestFn]]`)` */ protected override onRequestError(err: Error | RequestError, retry: RetryRequestFn): void { - const a = this.getPassedHandlers?.(); + const a = this.$attrs; - if (a == null || !a.has('error') && !a.has('error:component') && !a.has('onError')) { + if (a.onError == null && a.onOnError == null) { super.onRequestError(err, retry); } @@ -112,9 +112,9 @@ export default class bRemoteProvider extends iData { * @emits `addData(data: unknown)` */ protected override onAddData(data: unknown): void { - const a = this.getPassedHandlers?.(); + const a = this.$attrs; - if (a == null || !a.has('addData') && !a.has('addData:component') && !a.has('onAddData')) { + if (a.onAddData == null && a.onOnAddData == null) { return super.onAddData(data); } @@ -128,9 +128,9 @@ export default class bRemoteProvider extends iData { * @emits `updateData(data: unknown)` */ protected override onUpdateData(data: unknown): void { - const a = this.getPassedHandlers?.(); + const a = this.$attrs; - if (a != null && !a.has('updateData') && !a.has('updateData:component') && !a.has('onUpdateData')) { + if (a.onUpdateData == null && a.onOnUpdateData == null) { return super.onUpdateData(data); } @@ -144,9 +144,9 @@ export default class bRemoteProvider extends iData { * @emits `deleteData(data: unknown)` */ protected override onDeleteData(data: unknown): void { - const a = this.getPassedHandlers?.(); + const a = this.$attrs; - if (a == null || !a.has('deleteData') && !a.has('deleteData:component') && !a.has('onDeleteData')) { + if (a.onDeleteData == null && a.onOnDeleteData == null) { return super.onDeleteData(data); } diff --git a/src/components/base/b-tree/b-tree.ts b/src/components/base/b-tree/b-tree.ts index 65106bc92a..f051ae2775 100644 --- a/src/components/base/b-tree/b-tree.ts +++ b/src/components/base/b-tree/b-tree.ts @@ -185,7 +185,9 @@ class bTree extends iTreeProps implements iActiveItems, iFoldable { renderFilter }; - if (this.getPassedHandlers?.().has('fold')) { + const a = this.$attrs; + + if (a.onFold != null) { opts['@fold'] = this.$attrs.onFold; } diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 7e42944298..415fc9954e 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -199,15 +199,19 @@ export function mergeMods( declaredMods = component.meta.component.mods, expandedModsProp = {...modsProp}; - component.getPassedProps?.().forEach((name) => { - if (name in declaredMods) { - const attr = component.$attrs[name]; + const attrNames = Object.keys(component.$attrs); + + for (let i = 0; i < attrNames.length; i++) { + const attrName = attrNames[i]; + + if (attrName in declaredMods) { + const attr = component.$attrs[attrName]; if (attr != null) { - expandedModsProp[name] = attr; + expandedModsProp[attrName] = attr; } } - }); + } return expandedModsProp; } diff --git a/src/components/super/i-block/props.ts b/src/components/super/i-block/props.ts index c2f815d228..d70e139be1 100644 --- a/src/components/super/i-block/props.ts +++ b/src/components/super/i-block/props.ts @@ -112,7 +112,7 @@ export default abstract class iBlockProps extends ComponentInterface { /** * If set to false, the component will not render its content during SSR */ - @prop({type: Boolean, forceDefault: true}) + @prop({type: Boolean}) readonly ssrRenderingProp: boolean = true; /** @@ -309,7 +309,7 @@ export default abstract class iBlockProps extends ComponentInterface { @prop({type: Object, required: false}) override readonly styles?: Dictionary | Dictionary>; - @prop({type: Boolean, forceDefault: true}) + @prop({type: Boolean}) override readonly canFunctional: boolean = false; @prop({type: Function, required: false}) @@ -319,8 +319,5 @@ export default abstract class iBlockProps extends ComponentInterface { override readonly getParent?: () => this['$parent']; @prop({type: Function, required: false}) - override readonly getPassedProps?: () => Set; - - @prop({type: Function, required: false}) - override readonly getPassedHandlers?: () => Set; + override readonly getPassedProps?: () => Dictionary; } diff --git a/src/core/component/decorators/component/README.md b/src/core/component/decorators/component/README.md index 07d60aa106..0584628d9b 100644 --- a/src/core/component/decorators/component/README.md +++ b/src/core/component/decorators/component/README.md @@ -185,11 +185,6 @@ class bLink extends iData { < b-button-functional ``` -### [defaultProps = `true`] - -If set to false, all default values for the input properties of the component will be disregarded. -This parameter may be inherited from the parent component. - ### [deprecatedProps] A dictionary that specifies deprecated component props along with their recommended alternatives. diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index ee42131187..bc3b8d2934 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -88,11 +88,11 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p method = {...parent, src, fn}; if (parent.hooks != null) { - fn.hooks = Object.create(parent.hooks); + method.hooks = Object.create(parent.hooks); } if (parent.watchers != null) { - fn.watchers = Object.create(parent.watchers); + method.watchers = Object.create(parent.watchers); } } else { diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index 8c23930060..78095f6eb7 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -85,9 +85,7 @@ export function implementEventEmitterAPI(component: object): void { writable: false, value(event: string, ...args: unknown[]) { - const needNativeEvent = nativeEmit != null && !event.startsWith('[[') && ctx.getPassedHandlers?.().has(event); - - if (needNativeEvent) { + if (nativeEmit != null && !event.startsWith('[[')) { nativeEmit(event, ...args); } diff --git a/src/core/component/functional/context/inherit.ts b/src/core/component/functional/context/inherit.ts index d180082905..21d3b82fd8 100644 --- a/src/core/component/functional/context/inherit.ts +++ b/src/core/component/functional/context/inherit.ts @@ -36,17 +36,21 @@ export function inheritContext( linkedFields = {}; if (parentProps != null) { - for (const prop of parentProps) { - const linked = parentCtx.$syncLinkCache.get(prop); + const propNames = Object.keys(parentProps); + + for (let i = 0; i < propNames.length; i++) { + const + propName = propNames[i], + linked = parentCtx.$syncLinkCache.get(propName); if (linked != null) { const links = Object.values(linked); - for (let i = 0; i < links.length; i++) { - const link = links[i]; + for (let j = 0; j < links.length; j++) { + const link = links[j]; if (link != null) { - linkedFields[link.path] = prop; + linkedFields[link.path] = propName; } } } diff --git a/src/core/component/interface/component/component.ts b/src/core/component/interface/component/component.ts index 7f93239846..57bd54d6b5 100644 --- a/src/core/component/interface/component/component.ts +++ b/src/core/component/interface/component/component.ts @@ -163,14 +163,9 @@ export abstract class ComponentInterface { abstract readonly getParent?: () => this['$parent']; /** - * The getter is used to get a set of props that were passed to the component directly through the template + * The getter is used to get a dictionary of props that were passed to the component directly through the template */ - abstract readonly getPassedProps?: () => Set; - - /** - * The getter is used to get a set of event handlers that were passed to the component directly through the template - */ - abstract readonly getPassedHandlers?: () => Set; + abstract readonly getPassedProps?: () => Dictionary; /** * A string value indicating the lifecycle hook that the component is currently in. diff --git a/src/core/component/meta/interface/options.ts b/src/core/component/meta/interface/options.ts index 8d5e726f87..ffb74d1858 100644 --- a/src/core/component/meta/interface/options.ts +++ b/src/core/component/meta/interface/options.ts @@ -159,14 +159,6 @@ export interface ComponentOptions { */ functional?: Nullable | Dictionary>; - /** - * If set to false, all default values for the input properties of the component will be disregarded. - * This parameter may be inherited from the parent component. - * - * @default `true` - */ - defaultProps?: boolean; - /** * A dictionary that specifies deprecated component props along with their recommended alternatives. * The keys in the dictionary represent the deprecated props, diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index 7c5e6f3536..060ddab11a 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -16,7 +16,6 @@ import type { ComponentInterface, FieldWatcher, MethodWatcher, Hook } from 'core export interface ComponentProp extends PropOptions { forceUpdate: boolean; - forceDefault?: boolean; watchers?: Map; default?: unknown; diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index dabf376f4f..93e1891fe9 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -36,11 +36,15 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { el = unsafe.$el, propValuesToUpdate: string[][] = []; - unsafe.getPassedProps?.()?.forEach((attrName) => { - const prop = meta.props[attrName]; + const attrNames = Object.keys(unsafe.$attrs); + + for (let i = 0; i < attrNames.length; i++) { + const + attrName = attrNames[i], + prop = meta.props[attrName]; if ((prop == null || prop.forceUpdate) && !isPropGetter.test(attrName)) { - return; + continue; } const propPrefix = 'on:'; @@ -63,7 +67,7 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { } } } - }); + } if (propValuesToUpdate.length > 0) { nonFunctionalParent.$on('hook:beforeUpdate', updatePropsValues); diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index 1d471b5b56..b7741f9e48 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -61,12 +61,10 @@ export function initProps( let propValue = (from ?? component)[propName]; - if (propValue === undefined && unsafe.getPassedHandlers?.().has(`:${propName}`)) { - const getAccessors = unsafe.$attrs[`on:${propName}`]; + const getAccessors = unsafe.$attrs[`on:${propName}`]; - if (Object.isFunction(getAccessors)) { - propValue = getAccessors()[0]; - } + if (propValue === undefined && Object.isFunction(getAccessors)) { + propValue = getAccessors()[0]; } let needSaveToStore = opts.saveToStore; diff --git a/src/core/component/render/wrappers.ts b/src/core/component/render/wrappers.ts index a03ec6a650..4fbdf8b92f 100644 --- a/src/core/component/render/wrappers.ts +++ b/src/core/component/render/wrappers.ts @@ -140,31 +140,8 @@ export function wrapCreateBlock(original: T): T { vnode.virtualParent.value : this; - let passedProps: CanNull> = null; - props.getPassedProps ??= () => passedProps ??= new Set(attrs != null ? Object.keys(attrs) : []); - - let passedHandlers: CanNull> = null; - props.getPassedHandlers ??= () => { - if (passedHandlers != null) { - return passedHandlers; - } - - if (attrs == null) { - passedHandlers = new Set(); - - } else { - passedHandlers = new Set( - Object.keys(attrs) - .filter((prop) => prop.startsWith('on')) - .map((prop) => { - prop = prop.slice('on'.length); - return prop[0].toLowerCase() + prop.slice(1); - }) - ); - } - - return passedHandlers; - }; + let passedProps: Nullable = null; + props.getPassedProps ??= () => passedProps ??= attrs; // For refs within functional components, // it is necessary to explicitly set a reference to the instance of the component diff --git a/src/core/component/watch/helpers.ts b/src/core/component/watch/helpers.ts index 38fae5f09e..7d930e2c36 100644 --- a/src/core/component/watch/helpers.ts +++ b/src/core/component/watch/helpers.ts @@ -49,7 +49,7 @@ export function canSkipWatching( prop = meta.props[propInfo.name], propName = prop?.forceUpdate !== false ? propInfo.name : `on:${propInfo.name}`; - skipWatching = ctx.getPassedProps?.().has(propName) === false; + skipWatching = ctx.getPassedProps?.().hasOwnProperty(propName) === false; } } else { From bfdc47557709bb1d0ddb5ae943fb9b34c8e2c558 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 14:01:11 +0300 Subject: [PATCH 292/334] chore: provide `isPros` for vue sfc --- config/default.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/config/default.js b/config/default.js index ff24a7962d..e1db331e8c 100644 --- a/config/default.js +++ b/config/default.js @@ -46,7 +46,9 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { /** * Returns browserslist env + * * @param {string} env - custom environment + * @returns {string} */ browserslistEnv(env) { if (env == null) { @@ -1219,7 +1221,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { * @returns {object} */ compilerSFC() { - const {ssr} = this.config.webpack; + const {webpack} = this.config; const NOT_CONSTANT = 0, @@ -1232,7 +1234,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { (node) => { const {props} = node; - if (!ssr || props == null) { + if (!webpack.ssr || props == null) { return; } @@ -1317,9 +1319,10 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { ]; return { - ssr: this.config.webpack.ssr, - ssrCssVars: {}, - compilerOptions: {nodeTransforms} + isProd: webpack.mode() === 'production', + compilerOptions: {nodeTransforms}, + ssr: webpack.ssr, + ssrCssVars: {} }; } }, From 6d9ddf4550174d9ff07c3a91135680ecaa647ef4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 14:22:00 +0300 Subject: [PATCH 293/334] refactor: removed idle initializing --- src/core/component/decorators/component/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index c73106f8a1..5c2abe8580 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -118,9 +118,6 @@ export function component(opts?: ComponentOptions): Function { if (needRegisterImmediate) { registerComponent(componentFullName); - - } else { - requestIdleCallback(registerComponent.bind(null, componentFullName)); } // If we have a smart component, From 245f3497d792cab7c462effaf71c91fe25e91e33 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 15:34:29 +0300 Subject: [PATCH 294/334] chore: stylish fixes --- config/default.js | 73 ++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/config/default.js b/config/default.js index e1db331e8c..fcd1db153b 100644 --- a/config/default.js +++ b/config/default.js @@ -11,15 +11,13 @@ /* eslint-disable max-lines */ const - config = require('@v4fire/core/config/default'); + config = require('@v4fire/core/config/default'), + o = require('@v4fire/config/options').option; const fs = require('node:fs'), path = require('upath'); -const - o = require('@v4fire/config/options').option; - const browserslist = require('browserslist'), {nanoid} = require('nanoid'); @@ -45,9 +43,9 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { }, /** - * Returns browserslist env + * Returns the environment for Browserslist * - * @param {string} env - custom environment + * @param {string} [env] - custom environment * @returns {string} */ browserslistEnv(env) { @@ -70,7 +68,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { src: { /** - * Returns a path to the application dist directory for client scripts + * Returns the path to the application distribution directory for client-side scripts of the application * * @cli client-output * @env CLIENT_OUTPUT @@ -98,7 +96,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { }, /** - * Test server port + * The port number for the test server * @env TEST_PORT */ testPort: o('test-port', { @@ -107,7 +105,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { }), /** - * Option for configuring target environment for build, for example list of polyfills + * Option for specifying the target environment in the build process * * @cli build-edition * @env BUILD_EDITION @@ -183,7 +181,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { env: true, short: 'p', type: 'number', - default: require('os').cpus().length - 1 + default: require('node:os').cpus().length - 1 }), /** @@ -210,7 +208,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { * This information is used by WebPack for code block deduplication and build optimization, * but the graph calculation process may take some time. * - * This option allows to take the project graph from a previous build if it exists. + * This option allows taking the project graph from a previous build if it exists. * Keep in mind, an incorrect graph can break the application build. * * @cli build-graph-from-cache @@ -261,9 +259,11 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { }, /** - * Returns `true` if the application build times should be traced. - * Trace file will be created in the project's root. - * It's highly recommended to use this option with `module-parallelism=1`. + * Determines whether the application build times should be traced. + * If enabled, a trace file will be created in the project's root directory. + * + * Note: It is highly recommended to use this option with `module-parallelism=1` + * to ensure more accurate tracing results. * * @cli trace-build-times * @env TRACE_BUILD_TIMES @@ -334,7 +334,6 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { * @cli target * @env TARGET * - * @param {string} [def] - default value * @returns {?string} */ target() { @@ -581,7 +580,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { const aliases = { dompurify: this.config.es().toLowerCase() === 'es5' ? 'dompurify-v2' : 'dompurify-v3', 'vue/server-renderer': 'assets/lib/server-renderer.js', - // Disable setImmedate polyfill from core-js in favor of our realisation `core/shims/set-immediate` + // Disable setImmedate polyfill from core-js in favor of our realization `core/shims/set-immediate` 'core-js/modules/web.immediate.js': false }; @@ -725,8 +724,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { * ``` */ output(vars) { - const - res = this.mode() !== 'production' || this.fatHTML() ? '[name]' : '[hash]_[name]'; + const res = this.mode() !== 'production' || this.fatHTML() ? '[name]' : '[hash]_[name]'; if (vars) { return res.replace(/_?\[(.*?)]/g, (str, key) => { @@ -772,8 +770,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { * ``` */ assetsOutput(params) { - const - root = 'assets'; + const root = 'assets'; if (this.mode() !== 'production' || this.fatHTML()) { return this.output({ @@ -876,7 +873,10 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { }, /** - * SWC webpack loader configuration + * Returns parameters for `swc-loader` + * + * @param {string} env - custom environment + * @returns {{ts: object, js: object, ss: object}} */ swc(env) { const @@ -888,6 +888,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { jsc: { externalHelpers: true }, + env: { mode: 'entry', targets, @@ -904,13 +905,13 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { } }); - const - js = this.config.extend({}, base), - ss = this.config.extend({}, base, { - jsc: { - externalHelpers: false - } - }); + const js = this.config.extend({}, base); + + const ss = this.config.extend({}, base, { + jsc: { + externalHelpers: false + } + }); return {js, ts, ss}; } @@ -956,7 +957,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { }, /** - * Returns parameters for a TypeScript compiler: + * Returns parameters for the TypeScript compiler: * * 1. server - options for compiling the application as a node.js library; * 2. client - options for compiling the application as a client app. @@ -969,12 +970,9 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { 'client.tsconfig.json' : 'tsconfig.json'; - const - server = super.typescript(); + const server = super.typescript(); - const { - compilerOptions: {module} - } = require(path.join(this.src.cwd(), configFile)); + const {compilerOptions: {module}} = require(path.join(this.src.cwd(), configFile)); const client = this.extend({}, server, { configFile, @@ -992,10 +990,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { } }); - return { - client, - server - }; + return {client, server}; }, /** @@ -1007,7 +1002,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { }, /** - * Returns a component dependency map. + * Returns the component dependency map. * This map can be used to provide dynamic component dependencies in `index.js` files. * * @returns {object} From d7f14aaf76b62a266b3ccf765bc6a7d932aeceb2 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 15:45:48 +0300 Subject: [PATCH 295/334] refactor: use instance --- src/components/base/b-list/b-list.ts | 2 +- src/components/base/b-router/b-router.ts | 2 +- src/components/form/b-checkbox/b-checkbox.ts | 4 ++-- src/components/form/b-select/b-select.ts | 5 +---- .../pages/p-v4-components-demo/p-v4-components-demo.ts | 2 ++ src/components/super/i-block/base/index.ts | 4 +--- src/components/super/i-block/event/index.ts | 2 +- src/components/super/i-block/providers/index.ts | 5 +---- src/components/super/i-block/state/README.md | 2 +- src/components/super/i-block/state/index.ts | 2 +- src/components/super/i-input/i-input.ts | 9 ++------- src/core/component/decorators/hook/README.md | 2 +- 12 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/components/base/b-list/b-list.ts b/src/components/base/b-list/b-list.ts index fd35943f8b..910c3463a3 100644 --- a/src/components/base/b-list/b-list.ts +++ b/src/components/base/b-list/b-list.ts @@ -302,7 +302,7 @@ class bList extends iListProps implements iVisible, iWidth, iActiveItems { protected override initBaseAPI(): void { super.initBaseAPI(); - const i = (this.constructor).prototype; + const i = this.instance; this.isActive = i.isActive.bind(this); this.setActive = i.setActive.bind(this); diff --git a/src/components/base/b-router/b-router.ts b/src/components/base/b-router/b-router.ts index 7bab4f4010..a77203c3f0 100644 --- a/src/components/base/b-router/b-router.ts +++ b/src/components/base/b-router/b-router.ts @@ -424,7 +424,7 @@ export default class bRouter extends iRouterProps { protected override initBaseAPI(): void { super.initBaseAPI(); - const i = (this.constructor).prototype; + const i = this.instance; this.compileStaticRoutes = i.compileStaticRoutes.bind(this); this.emitTransition = i.emitTransition.bind(this); diff --git a/src/components/form/b-checkbox/b-checkbox.ts b/src/components/form/b-checkbox/b-checkbox.ts index ca21b0bb3c..7220f58c16 100644 --- a/src/components/form/b-checkbox/b-checkbox.ts +++ b/src/components/form/b-checkbox/b-checkbox.ts @@ -241,7 +241,7 @@ export default class bCheckbox extends iInput implements iSize { protected override initBaseAPI(): void { super.initBaseAPI(); - const i = (this.constructor).prototype; + const i = this.instance; this.convertValueToChecked = i.convertValueToChecked.bind(this); this.onCheckedChange = i.onCheckedChange.bind(this); @@ -285,7 +285,7 @@ export default class bCheckbox extends iInput implements iSize { } protected override resolveValue(value?: this['Value']): this['Value'] { - const i = (this.constructor).prototype; + const i = this.instance; const canApplyDefault = value === undefined && diff --git a/src/components/form/b-select/b-select.ts b/src/components/form/b-select/b-select.ts index b04cc8191e..b7095d3e34 100644 --- a/src/components/form/b-select/b-select.ts +++ b/src/components/form/b-select/b-select.ts @@ -441,10 +441,7 @@ class bSelect extends iSelectProps implements iOpenToggle, iActiveItems { protected override initBaseAPI(): void { super.initBaseAPI(); - - const i = (this.constructor).prototype; - - this.normalizeItems = i.normalizeItems.bind(this); + this.normalizeItems = this.instance.normalizeItems.bind(this); } /** {@link iOpenToggle.initCloseHelpers} */ diff --git a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts index 1f7884f7b6..7431b5a8b5 100644 --- a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts +++ b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts @@ -16,6 +16,8 @@ import VDOM, * as VDOMAPI from 'components/friends/vdom'; export * from 'components/super/i-static-page/i-static-page'; +console.time('init'); + VDOM.addToPrototype(VDOMAPI); /** diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 7a98e595c8..b75695e775 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -673,9 +673,7 @@ export default abstract class iBlockBase extends iBlockFriends { */ @hook('beforeRuntime') protected initBaseAPI(): void { - const i = (this.constructor).prototype; - - this.watch = i.watch.bind(this); + this.watch = this.instance.watch.bind(this); if (this.getParent != null) { const {$parent} = this; diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index af97ba85a4..4280a29e67 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -597,7 +597,7 @@ export default abstract class iBlockEvent extends iBlockBase { protected override initBaseAPI(): void { super.initBaseAPI(); - const i = (this.constructor).prototype; + const i = this.instance; this.on = i.on.bind(this); this.once = i.once.bind(this); diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 1068e464fd..66d1952304 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -368,9 +368,6 @@ export default abstract class iBlockProviders extends iBlockState { protected override initBaseAPI(): void { super.initBaseAPI(); - - const i = (this.constructor).prototype; - - this.createDataProviderInstance = i.createDataProviderInstance.bind(this); + this.createDataProviderInstance = this.instance.createDataProviderInstance.bind(this); } } diff --git a/src/components/super/i-block/state/README.md b/src/components/super/i-block/state/README.md index 0438df191b..afaba80fde 100644 --- a/src/components/super/i-block/state/README.md +++ b/src/components/super/i-block/state/README.md @@ -38,7 +38,7 @@ the [[iBlock]] class incorporates the following code. ``` @hook('beforeRuntime') protected initBaseAPI() { - const i = this.constructor.prototype; + const i = this.instance; this.syncStorageState = i.syncStorageState.bind(this); this.syncRouterState = i.syncRouterState.bind(this); diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 1c505b9ced..940bb7fda7 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -602,7 +602,7 @@ export default abstract class iBlockState extends iBlockMods { protected override initBaseAPI(): void { super.initBaseAPI(); - const i = (this.constructor).prototype; + const i = this.instance; this.i18n = i.i18n.bind(this); this.syncStorageState = i.syncStorageState.bind(this); diff --git a/src/components/super/i-input/i-input.ts b/src/components/super/i-input/i-input.ts index 1bfb1c2752..338c744dd9 100644 --- a/src/components/super/i-input/i-input.ts +++ b/src/components/super/i-input/i-input.ts @@ -334,10 +334,8 @@ export default abstract class iInput extends iInputHandlers implements iVisible, * @param [value] */ protected resolveValue(value?: this['Value']): this['Value'] { - const i = (this.constructor).prototype; - if (value === undefined && this.lfc.isBeforeCreate()) { - return i['defaultGetter'].call(this); + return this.instance['defaultGetter'].call(this); } return value; @@ -364,9 +362,6 @@ export default abstract class iInput extends iInputHandlers implements iVisible, protected override initBaseAPI(): void { super.initBaseAPI(); - - const i = (this.constructor).prototype; - - this.resolveValue = i.resolveValue.bind(this); + this.resolveValue = this.instance.resolveValue.bind(this); } } diff --git a/src/core/component/decorators/hook/README.md b/src/core/component/decorators/hook/README.md index e431772fd1..eaae6b696d 100644 --- a/src/core/component/decorators/hook/README.md +++ b/src/core/component/decorators/hook/README.md @@ -55,7 +55,7 @@ However, to use some methods before the `created` hook, the [[iBlock]] class has ``` @hook('beforeRuntime') protected initBaseAPI() { - const i = this.constructor.prototype; + const i = this.instance; this.syncStorageState = i.syncStorageState.bind(this); this.syncRouterState = i.syncRouterState.bind(this); From 94d0baa4aa101e730c86b2deab1ae59647a603fe Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 16:04:51 +0300 Subject: [PATCH 296/334] chore: fixed linters --- src/components/super/i-block/event/README.md | 4 ++-- src/components/super/i-block/providers/index.ts | 7 +++---- src/components/super/i-block/state/index.ts | 2 +- src/components/super/i-data/data.ts | 13 +++++++++++-- src/components/super/i-data/i-data.ts | 13 ++++++++++--- src/components/traits/i-open/i-open.ts | 2 +- src/components/traits/i-visible/i-visible.ts | 4 ++-- 7 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/components/super/i-block/event/README.md b/src/components/super/i-block/event/README.md index aa53a363b5..4c6bcfae7b 100644 --- a/src/components/super/i-block/event/README.md +++ b/src/components/super/i-block/event/README.md @@ -252,7 +252,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - declare readonly SelfEmitter: InferComponentEvents; @@ -279,7 +279,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - declare readonly SelfEmitter: InferComponentEvents; diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 66d1952304..f838e1b47a 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -38,14 +38,13 @@ import type { DataProviderProp } from 'components/super/i-block/providers/interf export * from 'components/super/i-block/providers/interface'; -const - $$ = symbolGenerator(); +const $$ = symbolGenerator(); @component({partial: 'iBlock'}) export default abstract class iBlockProviders extends iBlockState { /** @inheritDoc */ // @ts-ignore (override) - declare readonly SelfEmitter: InferComponentEvents; @@ -188,7 +187,7 @@ export default abstract class iBlockProviders extends iBlockState { route: this.route, globalName: component.globalName, component: component.componentName, - dataProvider: (component).dataProvider?.provider.constructor.name + dataProvider: Object.cast(component).dataProvider?.provider.constructor.name } } ); diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 940bb7fda7..9917da5e9a 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -40,7 +40,7 @@ import iBlockMods from 'components/super/i-block/mods'; export default abstract class iBlockState extends iBlockMods { /** @inheritDoc */ // @ts-ignore (override) - declare readonly SelfEmitter: InferComponentEvents {} @component({ @@ -49,6 +51,13 @@ interface iDataData extends Trait {} @derive(iDataProvider) abstract class iDataData extends iBlock implements iDataProvider { + /** @inheritDoc */ + // @ts-ignore (override) + declare readonly SelfEmitter!: InferComponentEvents], + ['dbChange', CanUndef], + ]>; + /** * Type: the raw provider data */ @@ -282,7 +291,7 @@ abstract class iDataData extends iBlock implements iDataProvider { protected override initModEvents(): void { super.initModEvents(); - iDataProvider.initModEvents(this); + iDataProvider.initModEvents(this); } } diff --git a/src/components/super/i-data/i-data.ts b/src/components/super/i-data/i-data.ts index aa3332d832..67fa539ad3 100644 --- a/src/components/super/i-data/i-data.ts +++ b/src/components/super/i-data/i-data.ts @@ -26,7 +26,9 @@ import { InitLoadCb, InitLoadOptions, - UnsafeGetter + + UnsafeGetter, + InferComponentEvents } from 'components/super/i-block/i-block'; @@ -55,11 +57,16 @@ export { export * from 'components/super/i-block/i-block'; export * from 'components/super/i-data/interface'; -const - $$ = symbolGenerator(); +const $$ = symbolGenerator(); @component({functional: null}) export default abstract class iData extends iDataHandlers { + /** @inheritDoc */ + // @ts-ignore (override) + declare readonly SelfEmitter!: InferComponentEvents; + @computed({functional: true}) override get unsafe(): UnsafeGetter> { return Object.cast(this); diff --git a/src/components/traits/i-open/i-open.ts b/src/components/traits/i-open/i-open.ts index 89cbdbc70a..22ba960ce0 100644 --- a/src/components/traits/i-open/i-open.ts +++ b/src/components/traits/i-open/i-open.ts @@ -140,7 +140,7 @@ export default abstract class iOpen { return; } - component.strictEmit(e.value === 'false' || e.type === 'remove' ? 'close' : 'open'); + component.emit(e.value === 'false' || e.type === 'remove' ? 'close' : 'open'); }); } diff --git a/src/components/traits/i-visible/i-visible.ts b/src/components/traits/i-visible/i-visible.ts index ce43d91789..67ed586a7e 100644 --- a/src/components/traits/i-visible/i-visible.ts +++ b/src/components/traits/i-visible/i-visible.ts @@ -51,11 +51,11 @@ export default abstract class iVisible { if (e.value === 'false' || e.type === 'remove') { $el?.setAttribute('aria-hidden', 'true'); - component.strictEmit('show'); + component.emit('show'); } else { $el?.setAttribute('aria-hidden', 'false'); - component.strictEmit('hide'); + component.emit('hide'); } }); } From bae7f061b834fae127c9d0bb26c3c99b810f337c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 18:44:42 +0300 Subject: [PATCH 297/334] chore: improved TS type inferring --- src/components/super/i-block/event/README.md | 4 +- .../super/i-block/event/interface.ts | 70 +++++-------------- .../super/i-block/providers/index.ts | 3 +- src/components/super/i-block/state/index.ts | 3 +- src/components/super/i-data/data.ts | 6 +- src/components/super/i-data/i-data.ts | 9 +-- 6 files changed, 26 insertions(+), 69 deletions(-) diff --git a/src/components/super/i-block/event/README.md b/src/components/super/i-block/event/README.md index 4c6bcfae7b..aa53a363b5 100644 --- a/src/components/super/i-block/event/README.md +++ b/src/components/super/i-block/event/README.md @@ -252,7 +252,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - declare readonly SelfEmitter!: InferComponentEvents; @@ -279,7 +279,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - declare readonly SelfEmitter!: InferComponentEvents; diff --git a/src/components/super/i-block/event/interface.ts b/src/components/super/i-block/event/interface.ts index a45ea878c9..bcc8805a0f 100644 --- a/src/components/super/i-block/event/interface.ts +++ b/src/components/super/i-block/event/interface.ts @@ -10,10 +10,15 @@ import type { LogLevel } from 'core/log'; export type InferEvents< Scheme extends Array<[string, ...any[]]>, + Parent extends Dictionary = {} +> = _InferEvents, Parent>; + +export type _InferEvents< + Scheme extends any[], Parent extends Dictionary = {}, Result extends Dictionary = {} > = { - 0: InferEvents, Parent, (TB.Head extends [infer E, ...infer A] ? E extends string ? { + 0: _InferEvents, Parent, (TB.Head extends [infer E, ...infer A] ? E extends string ? { on(event: E, cb: (...args: A) => void): void; once(event: E, cb: (...args: A) => void): void; promisifyOnce(event: E): Promise>>; @@ -33,21 +38,6 @@ export interface ComponentEvent { logLevel?: LogLevel; } -type UnionToIntersection = (T extends any ? (k: T) => void : never) extends (k: infer R) => void ? R : never; - -type EventToTuple = - UnionToIntersection Event : never> extends (() => infer T) ? - [...EventToTuple, Result>, [T, ...Result]] : - []; - -export type FlatEvents = { - 0: TB.Head extends [infer E, ...infer A] ? - FlatEvents, [...EventToTuple, ...Result]> : - []; - - 1: Result; -}[TB.Length extends 0 ? 1 : 0]; - export type InferComponentEvents< Ctx, Scheme extends Array<[string, ...any[]]>, @@ -58,7 +48,6 @@ export type _InferComponentEvents< Ctx, Scheme extends any[], Parent extends Dictionary = {}, - Events extends any[] = [], Result extends Dictionary = {} > = { 0: _InferComponentEvents< @@ -68,17 +57,15 @@ export type _InferComponentEvents< Parent, - TB.Head extends [infer E, ...infer A] ? [...Events, [E, ...A]] : Events, - (TB.Head extends [infer E, ...infer A] ? E extends string ? { on(event: `on${Capitalize}`, cb: (...args: A) => void): void; - on(event: E | `${E}:component`, cb: (component: Ctx, ...args: A) => void): void; + on(this: T, event: E | `${E}:component`, cb: (component: T, ...args: A) => void): void; once(event: `on${Capitalize}`, cb: (...args: A) => void): void; - once(event: E | `${E}:component`, cb: (component: Ctx, ...args: A) => void): void; + once(this: T, event: E | `${E}:component`, cb: (component: T, ...args: A) => void): void; promisifyOnce(event: `on${Capitalize}`): Promise>>; - promisifyOnce(event: E | `${E}:component`): Promise>; + promisifyOnce(this: T, event: E | `${E}:component`): Promise>; off(event: E | `${E}:component` | `on${Capitalize}` | string, handler?: Function): void; @@ -87,39 +74,20 @@ export type _InferComponentEvents< emit(event: string | ComponentEvent, ...args: unknown[]): void; } : {} : {}) & Result>; - 1: Parent extends {Events: infer ParentEvents} ? ParentEvents extends any[] ? - Overwrite< - Result & OverrideParentComponentEvents, - {Events: [...ParentEvents, ...Events]} - > : - - Result & {Events: Events} : Result & {Events: Events}; - + 1: Parent & Result; }[TB.Length extends 0 ? 1 : 0]; -export type OverrideParentComponentEvents< - Ctx, - Parent extends Dictionary, - Events extends any[], - Result extends Dictionary = {} -> = { - 0: TB.Head extends [infer E, ...infer A] ? E extends string ? - OverrideParentComponentEvents, Result & { - on(event: `on${Capitalize}`, cb: (...args: A) => void): void; - on(event: E | `${E}:component`, cb: (component: Ctx, ...args: A) => void): void; - - once(event: `on${Capitalize}`, cb: (...args: A) => void): void; - once(event: E | `${E}:component`, cb: (component: Ctx, ...args: A) => void): void; - - promisifyOnce(event: `on${Capitalize}`): Promise>>; - promisifyOnce(event: E | `${E}:component`): Promise>; +type UnionToIntersection = (T extends any ? (k: T) => void : never) extends (k: infer R) => void ? R : never; - off(event: E | `${E}:component` | `on${Capitalize}` | string, handler?: Function): void; +type EventToTuple = + UnionToIntersection Event : never> extends (() => infer T) ? + [...EventToTuple, Result>, [T, ...Result]] : + []; - strictEmit(event: E | ComponentEvent, ...args: A): void; - emit(event: E | ComponentEvent, ...args: A): void; - emit(event: string | ComponentEvent, ...args: unknown[]): void; - }> : Result : Result; +export type FlatEvents = { + 0: TB.Head extends [infer E, ...infer A] ? + FlatEvents, [...EventToTuple, ...Result]> : + []; 1: Result; }[TB.Length extends 0 ? 1 : 0]; diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index f838e1b47a..9dcd3079f7 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -43,8 +43,7 @@ const $$ = symbolGenerator(); @component({partial: 'iBlock'}) export default abstract class iBlockProviders extends iBlockState { /** @inheritDoc */ - // @ts-ignore (override) - declare readonly SelfEmitter!: InferComponentEvents; diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 9917da5e9a..522063d057 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -39,8 +39,7 @@ import iBlockMods from 'components/super/i-block/mods'; @component({partial: 'iBlock'}) export default abstract class iBlockState extends iBlockMods { /** @inheritDoc */ - // @ts-ignore (override) - declare readonly SelfEmitter!: InferComponentEvents {} @component({ @@ -52,11 +51,10 @@ interface iDataData extends Trait {} @derive(iDataProvider) abstract class iDataData extends iBlock implements iDataProvider { /** @inheritDoc */ - // @ts-ignore (override) - declare readonly SelfEmitter!: InferComponentEvents], ['dbChange', CanUndef], - ]>; + ], iBlock['SelfEmitter']>; /** * Type: the raw provider data diff --git a/src/components/super/i-data/i-data.ts b/src/components/super/i-data/i-data.ts index 67fa539ad3..448c22f22a 100644 --- a/src/components/super/i-data/i-data.ts +++ b/src/components/super/i-data/i-data.ts @@ -27,8 +27,7 @@ import { InitLoadCb, InitLoadOptions, - UnsafeGetter, - InferComponentEvents + UnsafeGetter } from 'components/super/i-block/i-block'; @@ -61,12 +60,6 @@ const $$ = symbolGenerator(); @component({functional: null}) export default abstract class iData extends iDataHandlers { - /** @inheritDoc */ - // @ts-ignore (override) - declare readonly SelfEmitter!: InferComponentEvents; - @computed({functional: true}) override get unsafe(): UnsafeGetter> { return Object.cast(this); From 66221b438ad714888363022b2be1caff5a97b6f5 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 18:49:27 +0300 Subject: [PATCH 298/334] chore: fixed linters --- .../pages/p-v4-components-demo/p-v4-components-demo.ts | 2 -- src/core/component/event/component.ts | 1 - src/core/component/interface/watch.ts | 1 - 3 files changed, 4 deletions(-) diff --git a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts index 7431b5a8b5..1f7884f7b6 100644 --- a/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts +++ b/src/components/pages/p-v4-components-demo/p-v4-components-demo.ts @@ -16,8 +16,6 @@ import VDOM, * as VDOMAPI from 'components/friends/vdom'; export * from 'components/super/i-static-page/i-static-page'; -console.time('init'); - VDOM.addToPrototype(VDOMAPI); /** diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index 78095f6eb7..a2ad9a0252 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -59,7 +59,6 @@ export function resetComponents(type?: ComponentResetType): void { */ export function implementEventEmitterAPI(component: object): void { const - ctx = Object.cast(component), unsafe = Object.cast(component), nativeEmit = Object.cast>(unsafe.$emit); diff --git a/src/core/component/interface/watch.ts b/src/core/component/interface/watch.ts index 218922e8c0..56fd7f60e0 100644 --- a/src/core/component/interface/watch.ts +++ b/src/core/component/interface/watch.ts @@ -39,7 +39,6 @@ export interface WatchOptions extends RawWatchOptions { } export interface FieldWatcher< - Ctx extends ComponentInterface = ComponentInterface, A = unknown, B = A > extends WatchOptions { From c3e93dda1a2b48e9b2c43cedfd0e37fb1437339f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 18:53:01 +0300 Subject: [PATCH 299/334] chore: fixed linters --- src/core/component/event/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/component/event/component.ts b/src/core/component/event/component.ts index a2ad9a0252..da3c8b3980 100644 --- a/src/core/component/event/component.ts +++ b/src/core/component/event/component.ts @@ -10,7 +10,7 @@ import type { EventId } from 'core/async'; import { EventEmitter2 as EventEmitter } from 'eventemitter2'; import * as gc from 'core/component/gc'; -import type { ComponentInterface, UnsafeComponentInterface, ComponentEmitterOptions } from 'core/component/interface'; +import type { UnsafeComponentInterface, ComponentEmitterOptions } from 'core/component/interface'; import { globalEmitter } from 'core/component/event/emitter'; import type { ComponentResetType } from 'core/component/event/interface'; From 7a127674df56c59502c0a0c3544a8d5cd6fa7be4 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 7 Nov 2024 18:56:57 +0300 Subject: [PATCH 300/334] refactor: simplify types --- src/components/super/i-block/event/README.md | 4 ++-- src/components/super/i-block/event/index.ts | 2 +- src/components/super/i-block/event/interface.ts | 6 +----- src/components/super/i-block/providers/index.ts | 2 +- src/components/super/i-block/state/index.ts | 2 +- src/components/super/i-data/data.ts | 2 +- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/components/super/i-block/event/README.md b/src/components/super/i-block/event/README.md index aa53a363b5..bf033aefca 100644 --- a/src/components/super/i-block/event/README.md +++ b/src/components/super/i-block/event/README.md @@ -252,7 +252,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - declare readonly SelfEmitter: InferComponentEvents; @@ -279,7 +279,7 @@ import iBlock, { component, InferComponentEvents } from 'components/super/i-bloc @component() export default class bExample extends iBlock { - declare readonly SelfEmitter: InferComponentEvents; diff --git a/src/components/super/i-block/event/index.ts b/src/components/super/i-block/event/index.ts index 4280a29e67..a434b1dd4e 100644 --- a/src/components/super/i-block/event/index.ts +++ b/src/components/super/i-block/event/index.ts @@ -44,7 +44,7 @@ export default abstract class iBlockEvent extends iBlockBase { * An associative type for typing events emitted by the component. * Events are described using tuples, where the first element is the event name, and the rest are arguments. */ - readonly SelfEmitter!: InferComponentEvents { } export type InferComponentEvents< - Ctx, Scheme extends Array<[string, ...any[]]>, Parent extends Dictionary = {} -> = _InferComponentEvents, Parent>; +> = _InferComponentEvents, Parent>; export type _InferComponentEvents< - Ctx, Scheme extends any[], Parent extends Dictionary = {}, Result extends Dictionary = {} > = { 0: _InferComponentEvents< - Ctx, - TB.Tail, Parent, diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 9dcd3079f7..fed2a859d0 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -43,7 +43,7 @@ const $$ = symbolGenerator(); @component({partial: 'iBlock'}) export default abstract class iBlockProviders extends iBlockState { /** @inheritDoc */ - declare readonly SelfEmitter: InferComponentEvents; diff --git a/src/components/super/i-block/state/index.ts b/src/components/super/i-block/state/index.ts index 522063d057..0eeac4928f 100644 --- a/src/components/super/i-block/state/index.ts +++ b/src/components/super/i-block/state/index.ts @@ -39,7 +39,7 @@ import iBlockMods from 'components/super/i-block/mods'; @component({partial: 'iBlock'}) export default abstract class iBlockState extends iBlockMods { /** @inheritDoc */ - declare readonly SelfEmitter: InferComponentEvents {} @derive(iDataProvider) abstract class iDataData extends iBlock implements iDataProvider { /** @inheritDoc */ - declare readonly SelfEmitter: InferComponentEvents], ['dbChange', CanUndef], ], iBlock['SelfEmitter']>; From 174a1fc31aef76fbc8d2ca60184fe3701a46190a Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 8 Nov 2024 13:36:45 +0300 Subject: [PATCH 301/334] chore: optimization --- .../super/i-block/providers/index.ts | 20 ++++----- src/components/super/i-data/data.ts | 42 +++++++++---------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index fed2a859d0..06ccb14a7b 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -27,7 +27,7 @@ import type iData from 'components/super/i-data/i-data'; import type iBlock from 'components/super/i-block/i-block'; import { statuses } from 'components/super/i-block/const'; -import { system, hook } from 'components/super/i-block/decorators'; +import { hook } from 'components/super/i-block/decorators'; import type { InitLoadCb, InitLoadOptions } from 'components/super/i-block/interface'; @@ -49,20 +49,16 @@ export default abstract class iBlockProviders extends iBlockState { ], iBlockState['SelfEmitter']>; /** {@link iBlock.dontWaitRemoteProvidersProp} */ - @system((o) => o.sync.link((val) => { - if (val == null) { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (o.dontWaitRemoteProviders != null) { - return o.dontWaitRemoteProviders; - } + get dontWaitRemoteProviders(): boolean { + const propVal = this.dontWaitRemoteProvidersProp; - return !config.components[o.componentName]?.dependencies.some((dep) => dep.includes('remote-provider')); + if (propVal == null) { + this[$$.dontWaitRemoteProviders] ??= !config.components[this.componentName]?.dependencies.some((dep) => dep.includes('remote-provider')); + return this[$$.dontWaitRemoteProviders]; } - return val; - })) - - dontWaitRemoteProviders!: boolean; + return propVal; + } /** * Loads component initialization data. diff --git a/src/components/super/i-data/data.ts b/src/components/super/i-data/data.ts index 148f97bcfe..5d0b883d80 100644 --- a/src/components/super/i-data/data.ts +++ b/src/components/super/i-data/data.ts @@ -22,6 +22,7 @@ import iBlock, { prop, field, system, + computed, watch, ModsDecl, @@ -95,15 +96,16 @@ abstract class iDataData extends iBlock implements iDataProvider { * These functions step by step transform the original provider data before storing it in `db`. * {@link iDataProvider.dbConverter} */ - @system((o) => o.sync.link('dbConverter', (val) => { - if (val == null) { + @computed({dependencies: ['dbConverter']}) + get dbConverters(): ComponentConverter[] { + const propVal = this.dbConverter; + + if (propVal == null) { return []; } - return Object.isIterable(val) ? [...val] : [val]; - })) - - dbConverters!: ComponentConverter[]; + return Object.isIterable(propVal) ? [...propVal] : [propVal]; + } /** * Converter(s) from the raw `db` to the component field. @@ -121,15 +123,16 @@ abstract class iDataData extends iBlock implements iDataProvider { * A list of converters from the raw `db` to the component field * {@link iDataProvider.componentConverter} */ - @system((o) => o.sync.link('componentConverter', (val) => { - if (val == null) { + @computed({dependencies: ['componentConverter']}) + get componentConverters(): ComponentConverter[] { + const propVal = this.componentConverter; + + if (propVal == null) { return []; } - return Object.isIterable(val) ? [...val] : [val]; - })) - - componentConverters!: ComponentConverter[]; + return Object.isIterable(propVal) ? [...propVal] : [propVal]; + } /** * A function to filter all "default" requests: all requests that were created implicitly, as the initial @@ -223,11 +226,9 @@ abstract class iDataData extends iBlock implements iDataProvider { protected convertDataToDB(data: unknown): O; protected convertDataToDB(data: unknown): this['DB']; protected convertDataToDB(data: unknown): O | this['DB'] { - const - {dbConverters} = this; + const {dbConverters} = this; - let - convertedData = data; + let convertedData = data; if (dbConverters.length > 0) { const rawData = Object.isArray(convertedData) || Object.isDictionary(convertedData) ? @@ -237,8 +238,7 @@ abstract class iDataData extends iBlock implements iDataProvider { convertedData = dbConverters.reduce((val, converter) => converter(val, Object.cast(this)), rawData); } - const - {db, checkDBEquality} = this; + const {db, checkDBEquality} = this; const canKeepOldData = Object.isFunction(checkDBEquality) ? Object.isTruly(checkDBEquality.call(this, convertedData, db)) : @@ -256,11 +256,9 @@ abstract class iDataData extends iBlock implements iDataProvider { * @param data */ protected convertDBToComponent(data: unknown): O | this['DB'] { - const - {componentConverters} = this; + const {componentConverters} = this; - let - convertedData = data; + let convertedData = data; if (componentConverters.length > 0) { const rawData = Object.isArray(convertedData) || Object.isDictionary(convertedData) ? From 11cb6b8f9d502b676f8780dea73d135fd244ac77 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 8 Nov 2024 15:09:24 +0300 Subject: [PATCH 302/334] chore: added log --- CHANGELOG.md | 49 ++++++++++++++++++++++ build/CHANGELOG.md | 13 ++++++ src/components/super/i-block/CHANGELOG.md | 8 ++++ src/core/component/accessor/CHANGELOG.md | 6 +++ src/core/component/decorators/CHANGELOG.md | 14 +++++++ src/core/component/interface/CHANGELOG.md | 6 +++ src/core/component/meta/CHANGELOG.md | 10 +++++ 7 files changed, 106 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ace1fbaabd..398cf6b358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,55 @@ Changelog _Note: Gaps between patch versions are faulty, broken or test releases._ +## v4.0.0-beta.?? (2024-??-??) + +#### :boom: Breaking Change + +* The `getPassedProps` method now returns a dictionary instead of a set `core/component` + +* `iBlock`: + * The API for `InferComponentEvents` has been changed so that you no longer need to pass this as the first argument + * Component events without an `error` or `warning` status are logged only if the `verbose` prop is set + * The `strictEmit` method no longer performs normalization of the event name + +* To automatically implement traits or characteristics for components, + the `derive` decorator from the `components/traits` module should now be used. + +#### :bug: Bug Fix + +* Fixed an error in normalizing attribute and prop values in Snakeskin `build` + +#### :rocket: New Feature + +* `core/component/decorators`: + * Added new decorators, defaultValue and method, for the class-based DSL. + These decorators are used during code generation by the TS transformer DSL. + + * The prop, field, and system decorators can now accept a default value for the field as a second argument. + This argument is used during code generation by the TS transformer DSL. + +#### :house: Internal + +* Various micro-optimizations + +* `build`: + * Added a new TypeScript transformer to automatically apply decorators to parts of a component: + methods, accessors, field overrides, etc. + * Now, only the value from the decorator is used to get the default field value. + Default values specified in the class property will automatically be passed to the decorator by the transformer. + +* The observation of accessor dependencies is now initialized only if the accessor has been used at least once `core/component/accessor` + +* The decorators from `core/component/decorators` no longer use a single factory module. + Now, each decorator is implemented independently. + +* `core/component/meta`: + * When inheriting metaobjects, prototype chains are now used instead of full copying. + This optimizes the process of creating metaobjects. + * Methods and accessors are now added to the metaobject via the `method` decorator instead of runtime reflection. + This decorator is automatically added during the build process. + * Optimized creation of metaobjects. + ## v4.0.0-beta.151 (2024-11-06) #### :bug: Bug Fix diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md index ebfd6c1295..bc3ba072db 100644 --- a/build/CHANGELOG.md +++ b/build/CHANGELOG.md @@ -9,6 +9,19 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :bug: Bug Fix + +* Fixed an error in normalizing attribute and prop values in Snakeskin + +#### :house: Internal + +* Added a new TypeScript transformer to automatically apply decorators to parts of a component: + methods, accessors, field overrides, etc. +* Now, only the value from the decorator is used to get the default field value. + Default values specified in the class property will automatically be passed to the decorator by the transformer. + ## v4.0.0-beta.138.dsl-speedup (2024-10-01) #### :house: Internal diff --git a/src/components/super/i-block/CHANGELOG.md b/src/components/super/i-block/CHANGELOG.md index 862f8e0288..6526f3820e 100644 --- a/src/components/super/i-block/CHANGELOG.md +++ b/src/components/super/i-block/CHANGELOG.md @@ -9,6 +9,14 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :boom: Breaking Change + +* The API for `InferComponentEvents` has been changed so that you no longer need to pass this as the first argument +* Component events without an `error` or `warning` status are logged only if the `verbose` prop is set +* The `strictEmit` method no longer performs normalization of the event name + ## v4.0.0-beta.139.dsl-speedup-2 (2024-10-03) #### :house: Internal diff --git a/src/core/component/accessor/CHANGELOG.md b/src/core/component/accessor/CHANGELOG.md index 0f62609cad..b88e47a051 100644 --- a/src/core/component/accessor/CHANGELOG.md +++ b/src/core/component/accessor/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :house: Internal + +* The observation of accessor dependencies is now initialized only if the accessor has been used at least once + ## v4.0.0-beta.138.dsl-speedup (2024-10-01) #### :rocket: New Feature diff --git a/src/core/component/decorators/CHANGELOG.md b/src/core/component/decorators/CHANGELOG.md index 9d7b24d932..d6c5ec07d0 100644 --- a/src/core/component/decorators/CHANGELOG.md +++ b/src/core/component/decorators/CHANGELOG.md @@ -9,6 +9,20 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :rocket: New Feature + +* Added new decorators, defaultValue and method, for the class-based DSL. + These decorators are used during code generation by the TS transformer DSL. + +* The prop, field, and system decorators can now accept a default value for the field as a second argument. + This argument is used during code generation by the TS transformer DSL. + +#### :house: Internal + +* The decorators from `core/component/decorators` no longer use a single factory module. Now, each decorator is implemented independently. + ## v4.0.0-beta.144 (2024-10-09) #### :bug: Bug Fix diff --git a/src/core/component/interface/CHANGELOG.md b/src/core/component/interface/CHANGELOG.md index e5b0920fbd..3b34ff0462 100644 --- a/src/core/component/interface/CHANGELOG.md +++ b/src/core/component/interface/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :boom: Breaking Change + +* The `getPassedProps` method now returns a dictionary instead of a set + ## v4.0.0-beta.139.dsl-speedup-2 (2024-10-03) #### :rocket: New Feature diff --git a/src/core/component/meta/CHANGELOG.md b/src/core/component/meta/CHANGELOG.md index 803648c0e8..53e515a6d2 100644 --- a/src/core/component/meta/CHANGELOG.md +++ b/src/core/component/meta/CHANGELOG.md @@ -9,6 +9,16 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :house: Internal + +* When inheriting metaobjects, prototype chains are now used instead of full copying. + This optimizes the process of creating metaobjects. +* Methods and accessors are now added to the metaobject via the `method` decorator instead of runtime reflection. + This decorator is automatically added during the build process. +* Optimized creation of metaobjects. + ## v4.0.0-beta.138.dsl-speedup (2024-10-01) #### :rocket: New Feature From 180274767321df3e631e7c839a188706194f7849 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 8 Nov 2024 15:12:08 +0300 Subject: [PATCH 303/334] chore: doc fixes --- src/components/traits/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/traits/README.md b/src/components/traits/README.md index 228ce38631..d5c2b2bbb8 100644 --- a/src/components/traits/README.md +++ b/src/components/traits/README.md @@ -1,7 +1,7 @@ # components/traits The module provides a set of traits for components. -A trait in TypeScript is an interface-like abstract class that serves as an interface. +A trait in TypeScript is an abstract class that serves as an interface. But why do we need it? Unlike Java or Kotlin, TypeScript interfaces cannot have default method implementations. Consequently, in TypeScript, we have to implement every method in our classes, From d60ef2151eb2d5bc11cf4f21b8fe1adce81e0393 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 8 Nov 2024 17:37:38 +0300 Subject: [PATCH 304/334] refactor: use only native idle --- src/core/component/decorators/component/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index 5c2abe8580..c5d0de8707 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -48,6 +48,8 @@ import type { ComponentConstructor, ComponentOptions } from 'core/component/inte const OVERRIDDEN = Symbol('This class is overridden in the child layer'); +const HAS_NATIVE_IDLE = requestIdleCallback.toString().includes('[native code]'); + /** * Registers a new component based on the tied class * @@ -118,6 +120,9 @@ export function component(opts?: ComponentOptions): Function { if (needRegisterImmediate) { registerComponent(componentFullName); + + } else if (HAS_NATIVE_IDLE) { + requestIdleCallback(registerComponent.bind(null, componentFullName)); } // If we have a smart component, From 28ffe28bccfd8420f6ad2dc924e5871b5b18b61b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 8 Nov 2024 19:25:21 +0300 Subject: [PATCH 305/334] chore: added memoization --- src/core/component/init/states/before-create.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/core/component/init/states/before-create.ts b/src/core/component/init/states/before-create.ts index 739164deff..2876f1c5a1 100644 --- a/src/core/component/init/states/before-create.ts +++ b/src/core/component/init/states/before-create.ts @@ -119,7 +119,9 @@ export function beforeCreateState( return ctx[$getRoot]; } - const fn = () => ('getRoot' in ctx ? ctx.getRoot?.() : null) ?? ctx.$root; + let fn = () => ('getRoot' in ctx ? ctx.getRoot?.() : null) ?? ctx.$root; + + fn = fn.once(); Object.defineProperty(ctx, $getRoot, { configurable: true, @@ -132,14 +134,18 @@ export function beforeCreateState( }) }); + let r: CanNull = null; + Object.defineProperty(unsafe, 'r', { configurable: true, enumerable: true, get: () => { - const r = ('getRoot' in unsafe ? unsafe.getRoot?.() : null) ?? unsafe.$root; + if (r == null) { + r = ('getRoot' in unsafe ? unsafe.getRoot?.() : null) ?? unsafe.$root; - if ('$remoteParent' in r.unsafe) { - return r.unsafe.$remoteParent!.$root; + if ('$remoteParent' in r.unsafe) { + r = r.unsafe.$remoteParent!.$root; + } } return r; @@ -173,6 +179,8 @@ export function beforeCreateState( fn = () => ctx; } + fn = fn.once(); + Object.defineProperty(targetCtx, $getParent, { configurable: true, enumerable: true, From 40ecc9253e191fc3cbb6cf5926f8b104cc40c405 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 8 Nov 2024 19:30:12 +0300 Subject: [PATCH 306/334] chore: removed idle --- src/core/component/decorators/component/index.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/core/component/decorators/component/index.ts b/src/core/component/decorators/component/index.ts index c5d0de8707..5c2abe8580 100644 --- a/src/core/component/decorators/component/index.ts +++ b/src/core/component/decorators/component/index.ts @@ -48,8 +48,6 @@ import type { ComponentConstructor, ComponentOptions } from 'core/component/inte const OVERRIDDEN = Symbol('This class is overridden in the child layer'); -const HAS_NATIVE_IDLE = requestIdleCallback.toString().includes('[native code]'); - /** * Registers a new component based on the tied class * @@ -120,9 +118,6 @@ export function component(opts?: ComponentOptions): Function { if (needRegisterImmediate) { registerComponent(componentFullName); - - } else if (HAS_NATIVE_IDLE) { - requestIdleCallback(registerComponent.bind(null, componentFullName)); } // If we have a smart component, From 1e848cfcd34902f2c21073a396b54df8ee872be3 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 01:44:20 +0300 Subject: [PATCH 307/334] feat: added a static property graph for use in synchronous code --- build/graph/graph.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/graph/graph.js b/build/graph/graph.js index 805a27b2af..4b2b3d06ff 100644 --- a/build/graph/graph.js +++ b/build/graph/graph.js @@ -71,6 +71,7 @@ async function buildProjectGraph() { if (build.buildGraphFromCache && fs.existsSync(graphCacheFile)) { const cache = loadFromCache(); buildFinished(); + module.exports.graph = await cache; return cache; } @@ -168,6 +169,7 @@ async function buildProjectGraph() { console.log('The project graph is initialized'); buildFinished(); + module.exports.graph = res; return res; /** From f30c0f99bb654e50f648b47c1810e66b06e27c28 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 01:45:04 +0300 Subject: [PATCH 308/334] BREAKING_CHANGE: the passing of the component graph to the runtime has been removed --- build/globals.webpack.js | 15 --------------- src/config/index.ts | 11 +---------- src/config/interface.ts | 2 -- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/build/globals.webpack.js b/build/globals.webpack.js index 902e0a1d58..6fc2def775 100644 --- a/build/globals.webpack.js +++ b/build/globals.webpack.js @@ -53,21 +53,6 @@ module.exports = { LANG_KEYSETS: s(collectI18NKeysets(locales)), LANG_PACKS: s(config.i18n.langPacksStore), - COMPONENTS: projectGraph.then(({components}) => { - if (Object.isMap(components)) { - return $C(components).to({}).reduce((res, el, key) => { - res[key] = { - parent: JSON.stringify(el.parent), - dependencies: JSON.stringify(el.dependencies) - }; - - return res; - }); - } - - return {}; - }), - BLOCK_NAMES: runtime.blockNames ? projectGraph.then(({components}) => { if (Object.isMap(components)) { diff --git a/src/config/index.ts b/src/config/index.ts index 5cdcf3828d..a927b781ff 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -41,14 +41,5 @@ extend({ USE_PROFILES: { html: true } - }, - - components: (() => { - try { - return COMPONENTS; - - } catch { - return {}; - } - })() + } }); diff --git a/src/config/interface.ts b/src/config/interface.ts index e70641aa87..bd7a469603 100644 --- a/src/config/interface.ts +++ b/src/config/interface.ts @@ -90,6 +90,4 @@ export interface Config extends SuperConfig { * For more information, see `components/directives/safe-html`. */ safeHtml: SanitizedOptions; - - components: typeof COMPONENTS; } From 6971afba317b21adeb4f9ca7768e8dcf67e29b9b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 01:46:35 +0300 Subject: [PATCH 309/334] chore: added support for the hint in dontWaitRemoteProviders --- .../register-component-parts/index.js | 50 +++++++++++++++++++ .../super/i-block/providers/README.md | 7 +-- .../super/i-block/providers/index.ts | 18 +++---- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/build/ts-transformers/register-component-parts/index.js b/build/ts-transformers/register-component-parts/index.js index 8d7777fb89..ac1e9f189f 100644 --- a/build/ts-transformers/register-component-parts/index.js +++ b/build/ts-transformers/register-component-parts/index.js @@ -10,6 +10,8 @@ const ts = require('typescript'); +const build = include('build/graph'); + const { addNamedImport, @@ -134,6 +136,18 @@ function resisterComponentDefaultValues(context) { originalComponentName = node.name.text; componentName = getPartialName(node) ?? originalComponentName; + const + normalizedComponentName = originalComponentName.dasherize(), + componentInfo = build.graph.components.get(componentName.dasherize()); + + if (normalizedComponentName !== 'i-block' && componentInfo != null) { + const hasRemoteProviders = componentInfo.dependencies.find((dep) => dep.includes('remote-provider')); + + if (hasRemoteProviders) { + node = addDontWaitRemoteProvidersHint(context, node); + } + } + if (node.members != null) { const newMembers = node.members.flatMap((node) => { if ( @@ -412,3 +426,39 @@ function addMethodDecorator(context, node) { node.body ); } + +/** + * Adds the `dontWaitRemoteProvidersHint` method to the specified component class + * + * @param {TransformationContext} context - the transformation context + * @param {Node} node - the class node in the AST + * @returns {Node} + */ +function addDontWaitRemoteProvidersHint(context, node) { + const {factory} = context; + + const method = factory.createMethodDeclaration( + undefined, + [ + ts.factory.createModifier(ts.SyntaxKind.ProtectedKeyword), + ts.factory.createModifier(ts.SyntaxKind.OverrideKeyword) + ], + undefined, + 'dontWaitRemoteProvidersHint', + undefined, + undefined, + [], + factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), + factory.createBlock([factory.createReturnStatement(factory.createTrue())]) + ); + + return factory.updateClassDeclaration( + node, + node.decorators, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + factory.createNodeArray([...node.members, method]) + ); +} diff --git a/src/components/super/i-block/providers/README.md b/src/components/super/i-block/providers/README.md index 56f9ba1cd9..39b3d240fb 100644 --- a/src/components/super/i-block/providers/README.md +++ b/src/components/super/i-block/providers/README.md @@ -35,7 +35,7 @@ class bExample extends iBlock { If true, the component is marked as a removed provider. This means that the parent component will wait for the current component to load. -#### [dontWaitRemoteProvidersProp] +#### [dontWaitRemoteProviders = `false`] If true, the component will skip waiting for remote providers to avoid redundant re-rendering. This prop can help optimize your non-functional component when it does not contain any remote providers. @@ -48,11 +48,6 @@ By default, this prop is automatically calculated based on component dependencie A list of additional dependencies to load during the component's initialization. The parameter is tied with the `dependenciesProp` prop. -#### dontWaitRemoteProviders - -If true, the component will skip waiting for remote providers to avoid redundant re-rendering. -The parameter is tied with the `dontWaitRemoteProvidersProp` prop. - ### Methods #### initLoad diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 06ccb14a7b..1217e3273e 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -11,7 +11,6 @@ * @packageDocumentation */ -import config from 'config'; import symbolGenerator from 'core/symbol'; import SyncPromise from 'core/promise/sync'; @@ -50,14 +49,7 @@ export default abstract class iBlockProviders extends iBlockState { /** {@link iBlock.dontWaitRemoteProvidersProp} */ get dontWaitRemoteProviders(): boolean { - const propVal = this.dontWaitRemoteProvidersProp; - - if (propVal == null) { - this[$$.dontWaitRemoteProviders] ??= !config.components[this.componentName]?.dependencies.some((dep) => dep.includes('remote-provider')); - return this[$$.dontWaitRemoteProviders]; - } - - return propVal; + return this.dontWaitRemoteProvidersProp ?? this.dontWaitRemoteProvidersHint(); } /** @@ -364,4 +356,12 @@ export default abstract class iBlockProviders extends iBlockState { super.initBaseAPI(); this.createDataProviderInstance = this.instance.createDataProviderInstance.bind(this); } + + /** + * Returns a hint on whether the component initialization mode can be used without waiting for remote providers. + * This method is overridden by a transformer at build time. + */ + protected dontWaitRemoteProvidersHint(): boolean { + return false; + } } From 081128a4b2335ababadf7788f71ad7ec477ea91b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 01:47:17 +0300 Subject: [PATCH 310/334] refactor: don't use config.componentes --- src/components/super/i-block/base/index.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index b75695e775..2c90a59a7c 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -28,12 +28,9 @@ import { } from 'core/async'; -import config from 'config'; - import { component, - getComponentName, getPropertyInfo, canSkipWatching, @@ -239,19 +236,19 @@ export default abstract class iBlockBase extends iBlockFriends { */ @computed({cache: 'forever'}) protected get componentI18nKeysets(): string[] { - const {constructor} = this.meta; + const {meta} = this; - let keysets: CanUndef = i18nKeysets.get(constructor); + let keysets: CanUndef = i18nKeysets.get(meta.constructor); if (keysets == null) { keysets = []; - i18nKeysets.set(constructor, keysets); + i18nKeysets.set(meta.constructor, keysets); - let keyset: CanUndef = getComponentName(constructor); + let {parentMeta} = meta; - while (keyset != null) { - keysets.push(keyset); - keyset = config.components[keyset]?.parent; + while (parentMeta != null) { + keysets.push(parentMeta.componentName); + parentMeta = parentMeta.parentMeta; } } From ef6b949b70023dcab52b0f551c1dfc0c6f4e6625 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 01:59:33 +0300 Subject: [PATCH 311/334] chore: removed dead code --- build/globals.webpack.js | 10 ---------- index.d.ts | 2 -- 2 files changed, 12 deletions(-) diff --git a/build/globals.webpack.js b/build/globals.webpack.js index 6fc2def775..a6ea2f469b 100644 --- a/build/globals.webpack.js +++ b/build/globals.webpack.js @@ -53,16 +53,6 @@ module.exports = { LANG_KEYSETS: s(collectI18NKeysets(locales)), LANG_PACKS: s(config.i18n.langPacksStore), - BLOCK_NAMES: runtime.blockNames ? - projectGraph.then(({components}) => { - if (Object.isMap(components)) { - const blockNames = Array.from(components.keys()).filter((el) => /^b-/.test(el)); - return s(blockNames); - } - }) : - - null, - THEME: s(config.theme.default()), THEME_ATTRIBUTE: s(config.theme.attribute), AVAILABLE_THEMES: pzlr.designSystem ? diff --git a/index.d.ts b/index.d.ts index e4a72652f9..760bf09845 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,9 +34,7 @@ declare const MODULE: string; declare const PATH: Dictionary>; declare const PUBLIC_PATH: CanUndef; -declare const COMPONENTS: Dictionary<{parent: string; dependencies: string[]}>; declare const TPLS: Dictionary>; -declare const BLOCK_NAMES: CanUndef; declare const THEME: CanUndef; declare const THEME_ATTRIBUTE: CanUndef; From ae1af2f77fbcd40a4f35f731757927a37a383a73 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 02:01:10 +0300 Subject: [PATCH 312/334] fix: fixed s typo --- build/ts-transformers/register-component-parts/index.js | 2 +- src/components/super/i-block/providers/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/ts-transformers/register-component-parts/index.js b/build/ts-transformers/register-component-parts/index.js index ac1e9f189f..4c76b149bf 100644 --- a/build/ts-transformers/register-component-parts/index.js +++ b/build/ts-transformers/register-component-parts/index.js @@ -449,7 +449,7 @@ function addDontWaitRemoteProvidersHint(context, node) { undefined, [], factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), - factory.createBlock([factory.createReturnStatement(factory.createTrue())]) + factory.createBlock([factory.createReturnStatement(factory.createFalse())]) ); return factory.updateClassDeclaration( diff --git a/src/components/super/i-block/providers/index.ts b/src/components/super/i-block/providers/index.ts index 1217e3273e..25fff1e762 100644 --- a/src/components/super/i-block/providers/index.ts +++ b/src/components/super/i-block/providers/index.ts @@ -362,6 +362,6 @@ export default abstract class iBlockProviders extends iBlockState { * This method is overridden by a transformer at build time. */ protected dontWaitRemoteProvidersHint(): boolean { - return false; + return true; } } From ad20b1667ab6dd14cc2ce123faebabe450d50810 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 02:11:37 +0300 Subject: [PATCH 313/334] chore: fixed linters --- build/globals.webpack.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/build/globals.webpack.js b/build/globals.webpack.js index a6ea2f469b..d44225b0a1 100644 --- a/build/globals.webpack.js +++ b/build/globals.webpack.js @@ -8,9 +8,7 @@ 'use strict'; -const - $C = require('collection.js'), - config = require('@config/config'); +const config = require('@config/config'); const {csp, build, webpack, i18n} = config, @@ -18,9 +16,7 @@ const {collectI18NKeysets} = include('build/helpers'), {getDSComponentMods, getThemes, getDS} = include('build/ds'); -const - projectGraph = include('build/graph'), - s = JSON.stringify; +const s = JSON.stringify; const locales = i18n.supportedLocales(), From 506b417f201fc2e1bf4718c9aa9c64e37a2d89bb Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 02:15:21 +0300 Subject: [PATCH 314/334] chore: updated log --- CHANGELOG.md | 3 +++ build/CHANGELOG.md | 4 ++++ src/config/CHANGELOG.md | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 398cf6b358..90efaf01d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ _Note: Gaps between patch versions are faulty, broken or test releases._ * To automatically implement traits or characteristics for components, the `derive` decorator from the `components/traits` module should now be used. +* Removed the constants `COMPONENTS` and `BLOCK_NAMES` `build/globals.webpack` +* Removed the `components` property `config` + #### :bug: Bug Fix * Fixed an error in normalizing attribute and prop values in Snakeskin `build` diff --git a/build/CHANGELOG.md b/build/CHANGELOG.md index bc3ba072db..8a47fe7bfa 100644 --- a/build/CHANGELOG.md +++ b/build/CHANGELOG.md @@ -11,6 +11,10 @@ Changelog ## v4.0.0-beta.?? (2024-??-??) +#### :boom: Breaking Change + +* Removed the constants `COMPONENTS` and `BLOCK_NAMES` `globals.webpack` + #### :bug: Bug Fix * Fixed an error in normalizing attribute and prop values in Snakeskin diff --git a/src/config/CHANGELOG.md b/src/config/CHANGELOG.md index fa05e02801..ae34f3111b 100644 --- a/src/config/CHANGELOG.md +++ b/src/config/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v4.0.0-beta.?? (2024-??-??) + +#### :boom: Breaking Change + +* Removed the `components` property + ## v4.0.0-beta.122 (2024-08-06) #### :rocket: New Feature From 3913e569619ee6e7547106a99a7e7586af65de3b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 02:20:28 +0300 Subject: [PATCH 315/334] chore: better name --- build/ts-transformers/register-component-parts/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/ts-transformers/register-component-parts/index.js b/build/ts-transformers/register-component-parts/index.js index 4c76b149bf..0c5739c507 100644 --- a/build/ts-transformers/register-component-parts/index.js +++ b/build/ts-transformers/register-component-parts/index.js @@ -29,7 +29,7 @@ const { * @typedef {import('typescript').ClassDeclaration} ClassDeclaration */ -module.exports = resisterComponentDefaultValues; +module.exports = registerComponentParts; /** * Registers parts of a class as parts of the associated component. @@ -88,7 +88,7 @@ module.exports = resisterComponentDefaultValues; * } * ``` */ -function resisterComponentDefaultValues(context) { +function registerComponentParts(context) { const {factory} = context; let From 9052b6e2589996bf4efa8c69fe291c56f5f9ae46 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 02:21:57 +0300 Subject: [PATCH 316/334] chore: don't store initializers --- build/ts-transformers/register-component-parts/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/ts-transformers/register-component-parts/index.js b/build/ts-transformers/register-component-parts/index.js index 0c5739c507..cf3b954835 100644 --- a/build/ts-transformers/register-component-parts/index.js +++ b/build/ts-transformers/register-component-parts/index.js @@ -346,7 +346,7 @@ function addDefaultValueDecorator(context, node) { node.name, node.questionToken, node.type, - node.initializer + undefined ); } @@ -357,7 +357,7 @@ function addDefaultValueDecorator(context, node) { node.name, node.questionToken, node.type, - node.initializer + undefined ); } From a9562713b10073b86d724ec77f477b2f760c7d3b Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 02:26:53 +0300 Subject: [PATCH 317/334] chore: added ExclamationToken --- build/ts-transformers/register-component-parts/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/ts-transformers/register-component-parts/index.js b/build/ts-transformers/register-component-parts/index.js index cf3b954835..728267a7e5 100644 --- a/build/ts-transformers/register-component-parts/index.js +++ b/build/ts-transformers/register-component-parts/index.js @@ -344,7 +344,7 @@ function addDefaultValueDecorator(context, node) { decorators, node.modifiers, node.name, - node.questionToken, + ts.SyntaxKind.ExclamationToken, node.type, undefined ); @@ -355,7 +355,7 @@ function addDefaultValueDecorator(context, node) { factory.createNodeArray(node.decorators), node.modifiers, node.name, - node.questionToken, + ts.SyntaxKind.ExclamationToken, node.type, undefined ); From 3c819c0ded524dbc1a8f260ddbc2fbdf4db50e0f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 02:44:40 +0300 Subject: [PATCH 318/334] chore: generate getters/setters dynamically --- .../register-component-parts/index.js | 32 ++++--------------- .../component/decorators/method/decorator.ts | 16 ++++++++++ 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/build/ts-transformers/register-component-parts/index.js b/build/ts-transformers/register-component-parts/index.js index 728267a7e5..6361767dbd 100644 --- a/build/ts-transformers/register-component-parts/index.js +++ b/build/ts-transformers/register-component-parts/index.js @@ -149,7 +149,7 @@ function registerComponentParts(context) { } if (node.members != null) { - const newMembers = node.members.flatMap((node) => { + const newMembers = node.members.map((node) => { if ( ts.isPropertyDeclaration(node) && ts.hasInitializer(node) && @@ -159,35 +159,15 @@ function registerComponentParts(context) { return addDefaultValueDecorator(context, node); } - const - isGetter = ts.isGetAccessorDeclaration(node), - isSetter = !isGetter && ts.isSetAccessorDeclaration(node); - - if (isGetter || isSetter || ts.isMethodDeclaration(node)) { + if ( + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node) || + ts.isMethodDeclaration(node) + ) { needImportMethodDecorator = true; node = addMethodDecorator(context, node); } - if (isGetter || ts.isSetAccessorDeclaration(node)) { - const - postfix = isGetter ? 'Getter' : 'Setter', - methodName = node.name.text + postfix; - - const method = factory.createMethodDeclaration( - undefined, - undefined, - undefined, - context.factory.createStringLiteral(methodName), - undefined, - undefined, - node.parameters, - undefined, - node.body - ); - - return [node, method]; - } - return node; }); diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index bc3b8d2934..752f2484ab 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -210,6 +210,22 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p // eslint-disable-next-line @v4fire/unbound-method get = desc.get ?? old?.get; + // To use `super` within the setter, we also create a new method with a name `${key}Setter` + if (set != null) { + const nm = `${name}Setter`; + + proto[nm] = set; + meta.methods[nm] = {src, fn: set}; + } + + // To using `super` within the getter, we also create a new method with a name `${key}Getter` + if (get != null) { + const nm = `${name}Getter`; + + proto[nm] = get; + meta.methods[nm] = {src, fn: get}; + } + let accessor: ComponentAccessor; if (store.hasOwnProperty(name)) { From d35c16189f6c5ef5f9784c39ff8d1ccbeab0a433 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Sat, 9 Nov 2024 03:56:10 +0300 Subject: [PATCH 319/334] fix: fixes after refactoring --- src/components/super/i-block/base/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/base/index.ts b/src/components/super/i-block/base/index.ts index 2c90a59a7c..2803c538a7 100644 --- a/src/components/super/i-block/base/index.ts +++ b/src/components/super/i-block/base/index.ts @@ -241,7 +241,7 @@ export default abstract class iBlockBase extends iBlockFriends { let keysets: CanUndef = i18nKeysets.get(meta.constructor); if (keysets == null) { - keysets = []; + keysets = [meta.componentName]; i18nKeysets.set(meta.constructor, keysets); let {parentMeta} = meta; From 5002600207bcaa93296f473957944c0d0c8c77d1 Mon Sep 17 00:00:00 2001 From: Andrey Kobets Date: Tue, 12 Nov 2024 13:57:34 +0300 Subject: [PATCH 320/334] Update CHANGELOG.md Co-authored-by: Artem Shinkaruk <46344555+shining-mind@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90efaf01d5..87c51cba19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ _Note: Gaps between patch versions are faulty, broken or test releases._ * The `getPassedProps` method now returns a dictionary instead of a set `core/component` * `iBlock`: - * The API for `InferComponentEvents` has been changed so that you no longer need to pass this as the first argument + * The API for `InferComponentEvents` has been changed so that you no longer need to pass `this` as the first argument * Component events without an `error` or `warning` status are logged only if the `verbose` prop is set * The `strictEmit` method no longer performs normalization of the event name From fabf43b8cf363a981b39335aee4f3bcd6b2a8114 Mon Sep 17 00:00:00 2001 From: Andrey Kobets Date: Wed, 13 Nov 2024 14:58:02 +0300 Subject: [PATCH 321/334] Speedup 3 1 shared mods (#1486) * BREAKING_CHANGE: removed legacy `inheritMods` implementation * chore: new `sharedMods` API * chore: added caching for shared-mods * chore: improved sharedMods api * fix: don't provide redundant mods * chore: optimization * chore: optimization * chore: optimization * chore: optimization * chore: added memoization * Revert "chore: added memoization" This reverts commit e9b5f3f11077263805f89df744845a2073a5a85a. * fix: fixed memoization * chore: optimized method creation * chore: reverted memoization * chore: implemented a smoother migration to `forceUpdate: false` props * Revert "chore: memoization of values in sharedMods" This reverts commit 8e752b06ed32c50d86081f196ee60375a33ab6dd. --- build/graph/component-params.js | 9 +- build/snakeskin/default-filters.js | 4 +- .../base/b-dynamic-page/b-dynamic-page.ts | 3 +- src/components/friends/block/README.md | 14 +- src/components/friends/field/delete.ts | 12 +- src/components/friends/field/get.ts | 12 +- src/components/friends/field/set.ts | 10 +- src/components/friends/provide/mods.ts | 22 ++- .../friends/provide/test/unit/main.ts | 9 +- src/components/super/i-block/mods/README.md | 22 +-- src/components/super/i-block/mods/index.ts | 7 +- .../super/i-block/modules/mods/index.ts | 130 ++++++++++++------ src/components/super/i-block/props.ts | 3 + .../component/decorators/method/decorator.ts | 15 -- .../decorators/prop/test/unit/force-update.ts | 26 ---- src/core/component/engines/vue3/component.ts | 1 - .../component/functional/context/create.ts | 3 - .../component/init/states/before-create.ts | 4 +- .../interface/component/component.ts | 8 +- src/core/component/meta/create.ts | 2 - src/core/component/meta/inherit.ts | 1 - src/core/component/meta/interface/meta.ts | 5 - src/core/component/meta/interface/options.ts | 4 +- src/core/component/method/index.ts | 13 +- src/core/component/prop/helpers.ts | 11 +- src/core/component/prop/init.ts | 30 ++-- src/core/component/watch/create.ts | 33 +++-- 27 files changed, 204 insertions(+), 209 deletions(-) diff --git a/build/graph/component-params.js b/build/graph/component-params.js index 2921194bfc..49ed084582 100644 --- a/build/graph/component-params.js +++ b/build/graph/component-params.js @@ -12,8 +12,7 @@ const $C = require('collection.js'), escaper = require('escaper'); -const - fs = require('node:fs'); +const fs = require('node:fs'); const { componentRgxp, @@ -67,8 +66,7 @@ Object.assign(componentParams, { * Load component runtime parameters to a map */ componentFiles.forEach((el) => { - const - escapedFragments = []; + const escapedFragments = []; const file = escaper.replace(fs.readFileSync(el).toString(), escapedFragments), @@ -94,8 +92,7 @@ componentFiles.forEach((el) => { parent }; - const - obj = componentParams[component]; + const obj = componentParams[component]; obj.deprecatedProps = p.deprecatedProps ?? {}; diff --git a/build/snakeskin/default-filters.js b/build/snakeskin/default-filters.js index aae98cf36e..690670a670 100644 --- a/build/snakeskin/default-filters.js +++ b/build/snakeskin/default-filters.js @@ -204,8 +204,8 @@ function tagFilter({name: tag, attrs = {}}, _, rootTag, forceRenderAsVNode, tplN attrs[':componentIdProp'] = [`componentId + ${JSON.stringify(id)}`]; } - if (component.inheritMods !== false && !attrs[':mods'] && !attrs[':modsProp']) { - attrs[':mods'] = ['provide.mods()']; + if (component.inheritMods !== false) { + attrs[':inheritMods'] = ['sharedMods != null']; } Object.entries(attrs).forEach(([name, val]) => { diff --git a/src/components/base/b-dynamic-page/b-dynamic-page.ts b/src/components/base/b-dynamic-page/b-dynamic-page.ts index d73d867ad3..b442efee28 100644 --- a/src/components/base/b-dynamic-page/b-dynamic-page.ts +++ b/src/components/base/b-dynamic-page/b-dynamic-page.ts @@ -325,7 +325,6 @@ export default class bDynamicPage extends iDynamicPage { if (passedProps != null) { const rejectedProps = { is: true, - keepAlive: true, dispatching: true, componentIdProp: true, getRoot: true, @@ -334,7 +333,7 @@ export default class bDynamicPage extends iDynamicPage { }; Object.entries(passedProps).forEach(([propName, prop]) => { - if (rejectedProps.hasOwnProperty(propName)) { + if (rejectedProps.hasOwnProperty(propName) || this.meta.props.hasOwnProperty(propName)) { return; } diff --git a/src/components/friends/block/README.md b/src/components/friends/block/README.md index 44266fedb4..55f0470427 100644 --- a/src/components/friends/block/README.md +++ b/src/components/friends/block/README.md @@ -233,7 +233,7 @@ To disable modifier inheritance, pass the `inheridMods: false` option when creat ```typescript import iBlock, { component } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock {} ``` @@ -246,7 +246,7 @@ Please note that the key must be in a dash style, i.e., normalized. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -266,7 +266,7 @@ This property can be observed using the watch API. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -310,7 +310,7 @@ To set a new modifier value or remove an old one, you must use the `setMod` and ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -343,7 +343,7 @@ inside and outside the component. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -380,7 +380,7 @@ this can be more convenient than handling each event individually. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -427,7 +427,7 @@ __b-example.ts__ ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { mounted() { const diff --git a/src/components/friends/field/delete.ts b/src/components/friends/field/delete.ts index 5eff50ffe1..008cd8380c 100644 --- a/src/components/friends/field/delete.ts +++ b/src/components/friends/field/delete.ts @@ -164,26 +164,26 @@ export function deleteField( prop = keyGetter ? keyGetter(chunks[0], ref) : chunks[0]; if (chunks.length > 1) { - chunks.some((chunk, i) => { + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + prop = keyGetter ? keyGetter(chunk, ref) : chunk; if (i + 1 === chunks.length) { - return true; + break; } const newRef = Object.isMap(ref) ? ref.get(prop) : ref[prop]; if (newRef == null || typeof newRef !== 'object') { needDelete = false; - return true; + break; } ref = newRef; - return false; - }); + } } - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (needDelete) { if (needDeleteToWatch) { ctx.$delete(ref, prop); diff --git a/src/components/friends/field/get.ts b/src/components/friends/field/get.ts index aabde4de90..c93158c02a 100644 --- a/src/components/friends/field/get.ts +++ b/src/components/friends/field/get.ts @@ -149,9 +149,11 @@ export function getField( } } else { - const hasNoProperty = chunks.some((chunk) => { + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + if (res == null) { - return true; + return undefined; } if (Object.isPromiseLike(res) && !(chunk in res)) { @@ -186,12 +188,6 @@ export function getField( res = typeof obj !== 'object' || chunk in obj ? obj[chunk] : undefined; } } - - return false; - }); - - if (hasNoProperty) { - return undefined; } } diff --git a/src/components/friends/field/set.ts b/src/components/friends/field/set.ts index 7ed6fe5770..df8c05dbb3 100644 --- a/src/components/friends/field/set.ts +++ b/src/components/friends/field/set.ts @@ -165,11 +165,13 @@ export function setField( let prop = keyGetter ? keyGetter(chunks[0], ref) : chunks[0]; if (chunks.length > 1) { - chunks.some((chunk, i) => { + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + prop = keyGetter ? keyGetter(chunk, ref) : chunk; if (i + 1 === chunks.length) { - return true; + break; } type AnyMap = Map; @@ -198,9 +200,7 @@ export function setField( } else { ref = ref[prop]; } - - return false; - }); + } } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition diff --git a/src/components/friends/provide/mods.ts b/src/components/friends/provide/mods.ts index cea49888a2..2e280f264b 100644 --- a/src/components/friends/provide/mods.ts +++ b/src/components/friends/provide/mods.ts @@ -33,24 +33,20 @@ import type { Mods } from 'components/friends/provide/interface'; * ``` */ export function mods(this: Friend, mods?: Mods): CanNull { - const {sharedMods} = this.ctx; - - if (sharedMods == null && mods == null) { + if (mods == null) { return null; } - const resolvedMods = {...sharedMods}; - - if (mods != null) { - const modNames = Object.keys(mods); + const + resolvedMods = {}, + modNames = Object.keys(mods); - for (let i = 0; i < modNames.length; i++) { - const - modName = modNames[i], - modVal = mods[modName]; + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + modVal = mods[modName]; - resolvedMods[modName.dasherize()] = modVal != null ? String(modVal) : undefined; - } + resolvedMods[modName.dasherize()] = modVal != null ? String(modVal) : undefined; } return resolvedMods; diff --git a/src/components/friends/provide/test/unit/main.ts b/src/components/friends/provide/test/unit/main.ts index 1becb0835d..a80d6541f9 100644 --- a/src/components/friends/provide/test/unit/main.ts +++ b/src/components/friends/provide/test/unit/main.ts @@ -89,14 +89,9 @@ test.describe('friends/provide', () => { }); test.describe('`mods`', () => { - test('should return a dictionary of active modifiers and their values', async () => { - await test.expect(target.evaluate(({provide}) => provide.mods())) - .resolves.toEqual({foo: 'bar'}); - }); - - test('should return a dictionary of active and provided modifiers and their values', async () => { + test('should return a dictionary of provided modifiers and their values', async () => { await test.expect(target.evaluate(({provide}) => provide.mods({baz: 'bla'}))) - .resolves.toEqual({foo: 'bar', baz: 'bla'}); + .resolves.toEqual({baz: 'bla'}); }); }); diff --git a/src/components/super/i-block/mods/README.md b/src/components/super/i-block/mods/README.md index 48590ec463..3a5a8d2fc9 100644 --- a/src/components/super/i-block/mods/README.md +++ b/src/components/super/i-block/mods/README.md @@ -188,11 +188,11 @@ To disable modifier inheritance, pass the `inheridMods: false` option when creat ```typescript import iBlock, { component } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component({inheridMods: false}) class bExample extends iBlock {} ``` -### Getting a Component's Modifier Value +### Getting a Component Modifier Value All component's applied modifiers are stored in the `mods` read-only property. Therefore, to get the value of any modifier, simply access the desired key. @@ -201,7 +201,7 @@ Note that the key should be in kebab case, i.e., normalized. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -221,7 +221,7 @@ This property can be observed using the watch API. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -258,14 +258,14 @@ If you want to use modifiers within a component template, then use the `m` gette ... ``` -### Setting a New Component's Modifier Value +### Setting a New Component Modifier Value To set a new modifier value or remove an existing one, you can use the `setMod` and `removeMod` methods. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -299,7 +299,7 @@ These events provide a way to react to changes in modifiers and perform any nece ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -336,7 +336,7 @@ it can be more convenient than handling each event separately. ```typescript import iBlock, { component, ModsDecl } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { static mods: ModsDecl = { theme: [ @@ -410,7 +410,7 @@ Note that the method returns the normalized value of the modifier. ```typescript import iBlock, { component } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { mounted() { this.setRootMod('foo', 'blaBar'); @@ -431,7 +431,7 @@ The method uses the component's `globalName` prop if provided, otherwise it uses ```typescript import iBlock, { component } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { mounted() { // this.componentName === 'b-button' && this.globalName === undefined @@ -453,7 +453,7 @@ The method uses the component `globalName` prop if it's provided. Otherwise, the ```typescript import iBlock, { component } from 'components/super/i-block/i-block'; -@component({inheritMods: false}) +@component() class bExample extends iBlock { mounted() { this.setRootMod('foo', 'bla'); diff --git a/src/components/super/i-block/mods/index.ts b/src/components/super/i-block/mods/index.ts index 937f6bfee4..c2de18d90f 100644 --- a/src/components/super/i-block/mods/index.ts +++ b/src/components/super/i-block/mods/index.ts @@ -26,13 +26,8 @@ export default abstract class iBlockMods extends iBlockEvent { @system({atom: true, merge: mergeMods, init: initMods}) override readonly mods!: ModsDict; + @computed({cache: 'forever'}) override get sharedMods(): CanNull { - const m = this.mods; - - if (m.theme != null) { - return {theme: m.theme}; - } - return null; } diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 415fc9954e..57f16ae255 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -25,46 +25,20 @@ export * from 'components/super/i-block/modules/mods/interface'; * @param component */ export function initMods(component: iBlock['unsafe']): ModsDict { - const - declMods = component.meta.component.mods, - resolveModVal = (val: unknown) => val != null ? String(val) : undefined; - - const - attrMods: Array<[string, () => CanUndef]> = [], - attrNames = Object.keys(component.$attrs); - - for (let i = 0; i < attrNames.length; i++) { - const - attrName = attrNames[i], - modName = attrName.camelize(false); - - if (modName in declMods) { - let el: Nullable; - - component.watch(`$attrs.${attrName}`, (attrs: Dictionary = {}) => { - el ??= component.$el; - - if (el instanceof Element) { - el.removeAttribute(attrName); - } - - void component.setMod(modName, resolveModVal(attrs[attrName])); - }); + const declMods = component.meta.component.mods; - component.meta.hooks['before:mounted'].push({ - fn: () => { - el = component.$el; + type RemoteMods = Array<[string, () => CanUndef]>; - if (el instanceof Element) { - el.removeAttribute(attrName); - } - } - }); + const + parentMods: RemoteMods = [], + attrMods: RemoteMods = []; - attrMods.push([modName, () => resolveModVal(component.$attrs[attrName])]); - } + if (component.inheritMods) { + initSharedMods(); } + initModsFromAttrs(); + return Object.cast(component.sync.link(link)); function link(propMods: CanUndef): ModsDict { @@ -72,6 +46,16 @@ export function initMods(component: iBlock['unsafe']): ModsDict { isModsInitialized = Object.isDictionary(component.mods), mods = isModsInitialized ? component.mods : {...declMods}; + for (let i = 0; i < parentMods.length; i++) { + const [modName, getModValue] = parentMods[i]; + + const modVal = getModValue(); + + if (modVal != null) { + mods[modName] = modVal; + } + } + if (propMods != null) { const propNames = Object.keys(propMods); @@ -87,12 +71,12 @@ export function initMods(component: iBlock['unsafe']): ModsDict { } for (let i = 0; i < attrMods.length; i++) { - const [attrName, getAttrValue] = attrMods[i]; + const [modName, getModValue] = attrMods[i]; - const attrVal = getAttrValue(); + const modVal = getModValue(); - if (isModsInitialized || attrVal != null) { - mods[attrName] = attrVal; + if (isModsInitialized || modVal != null) { + mods[modName] = modVal; } } @@ -137,6 +121,74 @@ export function initMods(component: iBlock['unsafe']): ModsDict { return mods; } + + function initSharedMods() { + const parent = component.$parent; + + if (parent == null) { + return; + } + + const {sharedMods} = parent; + + if (sharedMods == null) { + return; + } + + const modNames = Object.keys(sharedMods); + + for (let i = 0; i < modNames.length; i++) { + const modName = modNames[i]; + + component.watch(`$parent.mods.${modName}`, (mods: ModsDict) => { + void component.setMod(modName, mods[modName]); + }); + + parentMods.push([modName, () => parent.mods[modName]]); + } + } + + function initModsFromAttrs() { + const attrNames = Object.keys(component.$attrs); + + let el: Nullable; + + for (let i = 0; i < attrNames.length; i++) { + const + attrName = attrNames[i], + modName = attrName.camelize(false); + + if (modName in declMods) { + component.watch(`$attrs.${attrName}`, (attrs: Dictionary = {}) => { + el ??= component.$el; + + if (el instanceof Element) { + el.removeAttribute(attrName); + } + + void component.setMod(modName, resolveModVal(attrs[attrName])); + }); + + parentMods.push([modName, () => resolveModVal(component.$attrs[attrName])]); + } + } + + component.meta.hooks['before:mounted'].push({ + fn: () => { + el = component.$el; + + if (el instanceof Element) { + for (let i = 0; i < parentMods.length; i++) { + el.removeAttribute(parentMods[i][0]); + } + } + } + }); + } + + function resolveModVal(val: unknown) { + return val != null ? String(val) : undefined; + } } /** diff --git a/src/components/super/i-block/props.ts b/src/components/super/i-block/props.ts index d70e139be1..e4abcf24e6 100644 --- a/src/components/super/i-block/props.ts +++ b/src/components/super/i-block/props.ts @@ -57,6 +57,9 @@ export default abstract class iBlockProps extends ComponentInterface { @prop({type: Object, required: false, forceUpdate: false}) override readonly modsProp?: ModsProp; + @prop({type: Boolean, required: false}) + override readonly inheritMods?: boolean; + /** * If set to true, the component will be activated by default. * A deactivated component will not retrieve data from providers during initialization. diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index 752f2484ab..731db99793 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -10,8 +10,6 @@ import { defProp } from 'core/const/props'; import { createComponentDecorator3 } from 'core/component/decorators/helpers'; -import { getComponentContext } from 'core/component/context'; - import type { ComponentMeta } from 'core/component/meta'; import type { ComponentAccessor, ComponentMethod } from 'core/component/interface'; @@ -101,19 +99,6 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p } methods[name] = method; - component.methods[name] = fn; - - // eslint-disable-next-line func-style - const wrapper = function wrapper(this: object) { - // eslint-disable-next-line prefer-rest-params - return fn.apply(getComponentContext(this), arguments); - }; - - if (wrapper.length !== fn.length) { - Object.defineProperty(wrapper, 'length', {get: () => fn.length}); - } - - component.methods[name] = wrapper; const {hooks, watchers} = method; diff --git a/src/core/component/decorators/prop/test/unit/force-update.ts b/src/core/component/decorators/prop/test/unit/force-update.ts index eecdc9a684..38e6344d36 100644 --- a/src/core/component/decorators/prop/test/unit/force-update.ts +++ b/src/core/component/decorators/prop/test/unit/force-update.ts @@ -35,32 +35,6 @@ test.describe('contracts for props effects', () => { await test.expect(res).resolves.toBe(42); }); - - test.describe('an accessor error', () => { - test.beforeEach(async ({consoleTracker}) => { - await consoleTracker.setMessageFilters({ - 'No accessors are defined for the prop "dataProp".': (msg) => msg.args()[3].evaluate((err) => err.message) - }); - }); - - test('should be thrown if the value is not an accessor', async ({consoleTracker}) => { - await target.evaluate((ctx) => { - const vnode = ctx.vdom.create('b-non-effect-prop-dummy', {attrs: {dataProp: {a: 1}}}); - ctx.vdom.render(vnode); - }); - - await test.expect(consoleTracker.getMessages()).resolves.toHaveLength(1); - }); - - test('should not be thrown if the value is `undefined`', async ({consoleTracker}) => { - await target.evaluate((ctx) => { - const vnode = ctx.vdom.create('b-non-effect-prop-dummy', {attrs: {dataProp: undefined}}); - ctx.vdom.render(vnode); - }); - - await test.expect(consoleTracker.getMessages()).resolves.toHaveLength(0); - }); - }); }); test.describe('changing the value of the prop with `forceUpdate: false`', () => { diff --git a/src/core/component/engines/vue3/component.ts b/src/core/component/engines/vue3/component.ts index a1b1997f42..91bc957610 100644 --- a/src/core/component/engines/vue3/component.ts +++ b/src/core/component/engines/vue3/component.ts @@ -56,7 +56,6 @@ export function getComponent(meta: ComponentMeta): ComponentOptions; + protected readonly $attrs!: Dictionary; /** * A dictionary containing the watchable component fields that can trigger a re-rendering of the component diff --git a/src/core/component/meta/create.ts b/src/core/component/meta/create.ts index 1880faaae5..f33ef53fbc 100644 --- a/src/core/component/meta/create.ts +++ b/src/core/component/meta/create.ts @@ -88,9 +88,7 @@ export function createMeta(component: ComponentConstructorInfo): ComponentMeta { mods: {}, props: {}, attrs: {}, - computed: {}, - methods: {}, render() { throw new ReferenceError(`The render function for the component "${component.componentName}" is not specified`); diff --git a/src/core/component/meta/inherit.ts b/src/core/component/meta/inherit.ts index 9f172627fd..5d3ca69a64 100644 --- a/src/core/component/meta/inherit.ts +++ b/src/core/component/meta/inherit.ts @@ -42,7 +42,6 @@ export function inheritMeta(meta: ComponentMeta, parentMeta: ComponentMeta): Com meta.component.props = {...parentMeta.component.props}; meta.component.attrs = {...parentMeta.component.attrs}; meta.component.computed = {...parentMeta.component.computed}; - meta.component.methods = {...parentMeta.component.methods}; if (meta.params.partial == null) { inheritMods(meta, parentMeta); diff --git a/src/core/component/meta/interface/meta.ts b/src/core/component/meta/interface/meta.ts index d5d5be3d8d..b35e1e8bc2 100644 --- a/src/core/component/meta/interface/meta.ts +++ b/src/core/component/meta/interface/meta.ts @@ -192,11 +192,6 @@ export interface ComponentMeta { */ computed: Dictionary>>; - /** - * A dictionary containing the component methods - */ - methods: Dictionary; - /** * A dictionary containing the available component directives */ diff --git a/src/core/component/meta/interface/options.ts b/src/core/component/meta/interface/options.ts index ffb74d1858..a3362b885f 100644 --- a/src/core/component/meta/interface/options.ts +++ b/src/core/component/meta/interface/options.ts @@ -212,9 +212,7 @@ export interface ComponentOptions { inheritAttrs?: boolean; /** - * If set to true, the component will automatically inherit base modifiers from its parent component. - * This parameter may be inherited from the parent component. - * + * If set to true, the component will inherit modifiers from the parent `sharedMods` property * @default `true` */ inheritMods?: boolean; diff --git a/src/core/component/method/index.ts b/src/core/component/method/index.ts index e59db173eb..3dcefe4437 100644 --- a/src/core/component/method/index.ts +++ b/src/core/component/method/index.ts @@ -18,20 +18,17 @@ import type { ComponentInterface, UnsafeComponentInterface } from 'core/componen * @param component */ export function attachMethodsFromMeta(component: ComponentInterface): void { - const {meta, meta: {component: {methods}}} = Object.cast(component); + const {meta, meta: {methods}} = Object.cast(component); - const methodNames = Object.keys(methods); - - for (let i = 0; i < methodNames.length; i++) { - const - methodName = methodNames[i], - method = methods[methodName]; + // eslint-disable-next-line guard-for-in + for (const methodName in methods) { + const method = methods[methodName]; if (method == null) { continue; } - component[methodName] = method.bind(component); + component[methodName] = method.fn.bind(component); } if (meta.params.functional === true) { diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index 93e1891fe9..ad119f6c43 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -47,16 +47,7 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { continue; } - const propPrefix = 'on:'; - - if (prop != null) { - const getterName = propPrefix + attrName; - - if (unsafe.$attrs[attrName] !== undefined && !Object.isFunction(unsafe.$attrs[getterName])) { - throw new Error(`No accessors are defined for the prop "${attrName}". To set the accessors, pass them as ":${attrName} = propValue | @:${attrName} = createPropAccessors(() => propValue)()" or "v-attrs = {'@:${attrName}': createPropAccessors(() => propValue)}".`); - } - - } else { + if (prop == null) { const propName = isPropGetter.replace(attrName); if (meta.props[propName]?.forceUpdate === false) { diff --git a/src/core/component/prop/init.ts b/src/core/component/prop/init.ts index b7741f9e48..f47d94c4a4 100644 --- a/src/core/component/prop/init.ts +++ b/src/core/component/prop/init.ts @@ -42,7 +42,9 @@ export function initProps( isFunctional = meta.params.functional === true, source: typeof props = p.forceUpdate ? props : attrs; - const propNames = Object.keys(source); + const + propNames = Object.keys(source), + passedProps = unsafe.getPassedProps?.(); for (let i = 0; i < propNames.length; i++) { const @@ -61,7 +63,9 @@ export function initProps( let propValue = (from ?? component)[propName]; - const getAccessors = unsafe.$attrs[`on:${propName}`]; + const + accessorName = `on:${propName}`, + getAccessors = unsafe.$attrs[accessorName]; if (propValue === undefined && Object.isFunction(getAccessors)) { propValue = getAccessors()[0]; @@ -97,7 +101,7 @@ export function initProps( if (needSaveToStore) { const privateField = `[[${propName}]]`; - if (!opts.forceUpdate) { + if (!opts.forceUpdate && passedProps?.hasOwnProperty(accessorName)) { // Set the property as enumerable so that it can be deleted in the destructor later Object.defineProperty(store, privateField, { configurable: true, @@ -107,11 +111,21 @@ export function initProps( }); } - Object.defineProperty(store, propName, { - configurable: true, - enumerable: true, - get: () => opts.forceUpdate ? propValue : store[privateField] - }); + if (opts.forceUpdate) { + Object.defineProperty(store, propName, { + configurable: true, + enumerable: true, + writable: false, + value: propValue + }); + + } else { + Object.defineProperty(store, propName, { + configurable: true, + enumerable: true, + get: () => Object.hasOwn(store, privateField) ? store[privateField] : propValue + }); + } } } diff --git a/src/core/component/watch/create.ts b/src/core/component/watch/create.ts index 129624805e..b827c12484 100644 --- a/src/core/component/watch/create.ts +++ b/src/core/component/watch/create.ts @@ -334,7 +334,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface case 'prop': { const - prop = info.name, + propName = info.name, pathChunks = info.path.split('.'), slicedPathChunks = pathChunks.slice(1); @@ -347,7 +347,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface if (!forceUpdate) { const getAccessors: CanUndef> = Object.cast( - this.$attrs[`on:${prop}`] + this.$attrs[`on:${propName}`] ); accessors = getAccessors?.(); @@ -355,7 +355,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface const parent = component.$parent, - propVal = forceUpdate ? proxy[prop] : accessors?.[0]; + propVal = forceUpdate || accessors == null ? proxy[propName] : accessors[0]; if (parent == null || getProxyType(propVal) == null) { return; @@ -418,13 +418,20 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface } }; - const watcher = forceUpdate ? - watch(propVal, info.path, normalizedOpts, watchHandler) : - accessors?.[1](info.path, normalizedOpts, watchHandler); + let watcher: ReturnType; - if (watcher != null) { - destructors.push(watcher.unwatch.bind(watcher)); + if (forceUpdate) { + watcher = watch(propVal, info.path, normalizedOpts, watchHandler); + + } else { + if (accessors == null) { + throw new Error(`Accessors for observing the "${propName}" prop are not defined. To set the accessors, pass them as ":${propName} = propValue | @:${propName} = createPropAccessors(() => propValue)()" or "v-attrs = {'@:${propName}': createPropAccessors(() => propValue)}"`); + } + + watcher = accessors[1](info.path, normalizedOpts, watchHandler); } + + destructors.push(watcher.unwatch.bind(watcher)); }; const externalWatchHandler = (value: unknown, oldValue: unknown, i?: WatchHandlerParams) => { @@ -463,12 +470,12 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface let unwatch: Function; if (forceUpdate && 'watch' in watchInfo) { - unwatch = watchInfo.watch(prop, (value: object, oldValue?: object) => { + unwatch = watchInfo.watch(propName, (value: object, oldValue?: object) => { const info: WatchHandlerParams = { obj: component, root: component, - path: [prop], - originalPath: [prop], + path: [propName], + originalPath: [propName], top: value, fromProto: false }; @@ -506,7 +513,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface if (forceUpdate) { // eslint-disable-next-line @v4fire/unbound-method - unwatch = watch(proxy, prop, topOpts, Object.cast(externalWatchHandler)).unwatch; + unwatch = watch(proxy, propName, topOpts, Object.cast(externalWatchHandler)).unwatch; } else { if (topOpts.immediate) { @@ -514,7 +521,7 @@ export function createWatchFn(component: ComponentInterface): ComponentInterface delete topOpts.immediate; } - unwatch = watchFn.call(this, `[[${prop}]]`, topOpts, externalWatchHandler); + unwatch = watchFn.call(this, `[[${propName}]]`, topOpts, externalWatchHandler); } } From 2c0edeb242baa1b90600101f32f8673a31af35e1 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 13 Nov 2024 15:10:02 +0300 Subject: [PATCH 322/334] chore: methods for accessors, such as fooGetter/fooSetter, are used only with super, so it's not necessary to initialize them as a method on the component --- src/core/component/decorators/method/decorator.ts | 4 ++-- src/core/component/meta/interface/types.ts | 1 + src/core/component/method/index.ts | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index 731db99793..f024dfa462 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -200,7 +200,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p const nm = `${name}Setter`; proto[nm] = set; - meta.methods[nm] = {src, fn: set}; + meta.methods[nm] = {src, fn: set, accessor: true}; } // To using `super` within the getter, we also create a new method with a name `${key}Getter` @@ -208,7 +208,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p const nm = `${name}Getter`; proto[nm] = get; - meta.methods[nm] = {src, fn: get}; + meta.methods[nm] = {src, fn: get, accessor: true}; } let accessor: ComponentAccessor; diff --git a/src/core/component/meta/interface/types.ts b/src/core/component/meta/interface/types.ts index 060ddab11a..51978444ff 100644 --- a/src/core/component/meta/interface/types.ts +++ b/src/core/component/meta/interface/types.ts @@ -69,6 +69,7 @@ export interface ComponentMethod { src?: string; wrapper?: boolean; + accessor?: boolean; watchers?: Dictionary; hooks?: ComponentMethodHooks; diff --git a/src/core/component/method/index.ts b/src/core/component/method/index.ts index 3dcefe4437..b8ded54961 100644 --- a/src/core/component/method/index.ts +++ b/src/core/component/method/index.ts @@ -24,7 +24,9 @@ export function attachMethodsFromMeta(component: ComponentInterface): void { for (const methodName in methods) { const method = methods[methodName]; - if (method == null) { + // Methods for accessors, such as fooGetter/fooSetter, + // are used only with super, so it's not necessary to initialize them as a method on the component + if (method == null || method.accessor) { continue; } From 59d4e71e5171fb0a3f7bc54d50c5a2cb5693afdd Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 13 Nov 2024 15:27:05 +0300 Subject: [PATCH 323/334] refactor: simplify code --- .../decorators/default-value/decorator.ts | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index 30646a1b74..ac068e4ced 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -46,27 +46,28 @@ export function defaultValue(getter: unknown): PartDecorator { if (meta.props[key] != null) { regProp(key, {default: isFunction ? getter() : getter}, meta); - } else if (meta.fields[key] != null) { - const params = isFunction ? - {init: getter, default: undefined} : - {init: undefined, default: getter}; + } else { + const + isField = key in meta.fields, + isSystemField = !isField && key in meta.systemFields; - regField(key, 'fields', params, meta); + if (isField || isSystemField) { + const cluster = isField ? 'fields' : 'systemFields'; - } else if (meta.systemFields[key] != null) { - const params = isFunction ? - {init: getter, default: undefined} : - {init: undefined, default: getter}; + const params = isFunction ? + {init: getter, default: undefined} : + {init: undefined, default: getter}; - regField(key, 'systemFields', params, meta); + regField(key, cluster, params, meta); - } else if (isFunction) { - Object.defineProperty(meta.constructor.prototype, key, { - configurable: true, - enumerable: false, - writable: true, - value: getter() - }); + } else if (isFunction) { + Object.defineProperty(meta.constructor.prototype, key, { + configurable: true, + enumerable: false, + writable: true, + value: getter() + }); + } } }); } From 5da52b4660a66108d9eb4c4d2538921032cb0556 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 13 Nov 2024 15:35:23 +0300 Subject: [PATCH 324/334] chore: improved doc --- src/core/component/accessor/index.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/component/accessor/index.ts b/src/core/component/accessor/index.ts index 7e32b10a4a..3b0c4177cf 100644 --- a/src/core/component/accessor/index.ts +++ b/src/core/component/accessor/index.ts @@ -123,9 +123,6 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { if (deps != null && deps.length > 0 || tiedWith != null) { onCreated(this.hook, () => { - // If a computed property has a field or system field as a dependency - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching if (deps != null) { for (let i = 0; i < deps.length; i++) { const @@ -133,6 +130,9 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { path = Object.isArray(dep) ? dep.join('.') : String(dep), info = getPropertyInfo(path, component); + // If a computed property has a field or system field as a dependency + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching const needForceWatch = (info.type === 'system' || info.type === 'field') && @@ -147,11 +147,11 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { } if (tiedWith != null) { - const needForceWatch = watchers[tiedWith] == null && accessor.dependencies?.length !== 0; - // If a computed property is tied with a field or system field // and the host component does not have any watchers to this field, // we need to register a "fake" watcher to enforce watching + const needForceWatch = watchers[tiedWith] == null && accessor.dependencies?.length !== 0; + if (needForceWatch) { this.$watch(tiedWith, {deep: true, immediate: true}, fakeHandler); } @@ -223,9 +223,6 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { if (deps != null && deps.length > 0 || tiedWith != null) { onCreated(this.hook, () => { - // If a computed property has a field or system field as a dependency - // and the host component does not have any watchers to this field, - // we need to register a "fake" watcher to enforce watching if (deps != null) { for (let i = 0; i < deps.length; i++) { const @@ -254,6 +251,9 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { }); } + // If a computed property has a field or system field as a dependency + // and the host component does not have any watchers to this field, + // we need to register a "fake" watcher to enforce watching const needToForceWatching = (info.type === 'system' || info.type === 'field') && @@ -275,11 +275,11 @@ export function attachAccessorsFromMeta(component: ComponentInterface): void { } }); - const needToForceWatching = watchers[tiedWith] == null && computed.dependencies?.length !== 0; - // If a computed property is tied with a field or system field // and the host component does not have any watchers to this field, // we need to register a "fake" watcher to enforce watching + const needToForceWatching = watchers[tiedWith] == null && computed.dependencies?.length !== 0; + if (needToForceWatching) { this.$watch(tiedWith, {deep: true, immediate: true}, fakeHandler); } From 20c3889a33564cf0707b203f29ac6d154a1664f6 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 13 Nov 2024 15:41:54 +0300 Subject: [PATCH 325/334] refactor: better structure --- .../component/decorators/method/decorator.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/core/component/decorators/method/decorator.ts b/src/core/component/decorators/method/decorator.ts index f024dfa462..3dbd28c2b3 100644 --- a/src/core/component/decorators/method/decorator.ts +++ b/src/core/component/decorators/method/decorator.ts @@ -59,22 +59,22 @@ export function method(type: MethodType): PartDecorator { * @param proto - the prototype of the class where the method or accessor is defined */ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, proto: object): void { - const { - component, - componentName: src, + const {componentName: src} = meta; - props, - fields, - systemFields, + if (type === 'method') { + regMethod(); - methods - } = meta; + } else { + regAccessor(); + } - if (type === 'method') { - let method: ComponentMethod; + function regMethod() { + const {methods} = meta; const fn = proto[name]; + let method: ComponentMethod; + if (methods.hasOwnProperty(name)) { method = methods[name]!; method.fn = fn; @@ -141,14 +141,17 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p } }); } + } - } else { + function regAccessor() { const desc = Object.getOwnPropertyDescriptor(proto, name); if (desc == null) { return; } + const {props, fields, systemFields} = meta; + const propKey = `${name}Prop`, storeKey = `${name}Store`; @@ -232,7 +235,7 @@ export function regMethod(name: string, type: MethodType, meta: ComponentMeta, p store[name] = accessor; if (accessor.cache === 'auto') { - component.computed[name] = { + meta.component.computed[name] = { get: accessor.get, set: accessor.set }; From a97f04cdeed4b225c3ae9adf5f9cfcf01b409d05 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 13 Nov 2024 16:44:28 +0300 Subject: [PATCH 326/334] fix: fixed a typo --- src/components/super/i-block/modules/mods/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 57f16ae255..65881fc78b 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -169,7 +169,7 @@ export function initMods(component: iBlock['unsafe']): ModsDict { void component.setMod(modName, resolveModVal(attrs[attrName])); }); - parentMods.push([modName, () => resolveModVal(component.$attrs[attrName])]); + attrMods.push([modName, () => resolveModVal(component.$attrs[attrName])]); } } @@ -257,7 +257,7 @@ export function mergeMods( const attrName = attrNames[i]; if (attrName in declaredMods) { - const attr = component.$attrs[attrName]; + const attr = >component.$attrs[attrName]; if (attr != null) { expandedModsProp[attrName] = attr; From 64614713602d5ca51129bdb3e6cda014d76ea356 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Wed, 13 Nov 2024 16:54:20 +0300 Subject: [PATCH 327/334] fix: fixed linters & optimization --- src/components/super/i-block/i-block.ts | 45 +++++++++++-------- .../component/decorators/prop/decorator.ts | 1 - .../component/decorators/system/decorator.ts | 1 - 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/components/super/i-block/i-block.ts b/src/components/super/i-block/i-block.ts index 2010a254bb..fea1f89ac3 100644 --- a/src/components/super/i-block/i-block.ts +++ b/src/components/super/i-block/i-block.ts @@ -128,7 +128,7 @@ export default abstract class iBlock extends iBlockProviders { this.watch('$attrs', {deep: true}, mountAttrs); } - function mountAttrs(attrs: Dictionary) { + function mountAttrs(attrs: Dictionary) { const mountedAttrsGroup = {group: 'mountedAttrs'}; $a.terminateWorker(mountedAttrsGroup); @@ -137,34 +137,41 @@ export default abstract class iBlock extends iBlockProviders { } attrsStore ??= new Set(); - const mountedAttrs = attrsStore; - Object.entries(attrs).forEach(([name, attr]) => { - if (attr == null) { - return; + const + mountedAttrs = attrsStore, + attrNames = Object.keys(attrs); + + for (let i = 0; i < attrNames.length; i++) { + const + attrName = attrNames[i], + attrVal = attrs[attrName]; + + if (attrVal == null) { + continue; } - if (name === 'class') { - attr.split(/\s+/).forEach((val) => { - node.classList.add(val); - mountedAttrs.add(`class.${val}`); - }); + if (attrName === 'class') { + for (const className of (attrVal).split(/\s+/)) { + node.classList.add(className); + mountedAttrs.add(`class.${className}`); + } - } else if (originalNode.hasAttribute(name)) { - node.setAttribute(name, attr); - mountedAttrs.add(name); + } else if (originalNode.hasAttribute(attrName)) { + node.setAttribute(attrName, attrVal); + mountedAttrs.add(attrName); } - }); + } $a.worker(() => { - mountedAttrs.forEach((attr) => { - if (attr.startsWith('class.')) { - node.classList.remove(attr.split('.')[1]); + for (const attrName of mountedAttrs) { + if (attrName.startsWith('class.')) { + node.classList.remove(attrName.split('.')[1]); } else { - node.removeAttribute(attr); + node.removeAttribute(attrName); } - }); + } mountedAttrs.clear(); }, mountedAttrsGroup); diff --git a/src/core/component/decorators/prop/decorator.ts b/src/core/component/decorators/prop/decorator.ts index 02792d4f16..c083f9fff2 100644 --- a/src/core/component/decorators/prop/decorator.ts +++ b/src/core/component/decorators/prop/decorator.ts @@ -89,7 +89,6 @@ export function regProp(propName: string, typeOrParams: Nullable Date: Wed, 13 Nov 2024 17:01:25 +0300 Subject: [PATCH 328/334] fix: mods initialization --- src/components/super/i-block/modules/mods/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 65881fc78b..46191c5007 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -51,7 +51,7 @@ export function initMods(component: iBlock['unsafe']): ModsDict { const modVal = getModValue(); - if (modVal != null) { + if (isModsInitialized || modVal != null) { mods[modName] = modVal; } } From 8e65033b85e72b1daea0bf7879baed1187b8f944 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 14 Nov 2024 15:27:11 +0300 Subject: [PATCH 329/334] fix: mods merging --- .../super/i-block/modules/mods/index.ts | 245 +++++++++--------- 1 file changed, 119 insertions(+), 126 deletions(-) diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 46191c5007..0e0dbc00e8 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -30,126 +30,141 @@ export function initMods(component: iBlock['unsafe']): ModsDict { type RemoteMods = Array<[string, () => CanUndef]>; const - parentMods: RemoteMods = [], - attrMods: RemoteMods = []; + sharedMods = initSharedMods(), + attrMods = initAttrMods(); - if (component.inheritMods) { - initSharedMods(); - } + return component.sync.link(link)!; - initModsFromAttrs(); + function link(modsProp: CanUndef): ModsDict { + const isModsInitialized = Object.isDictionary(component.mods); - return Object.cast(component.sync.link(link)); + const mods = isModsInitialized ? component.mods : {...declMods}; - function link(propMods: CanUndef): ModsDict { - const - isModsInitialized = Object.isDictionary(component.mods), - mods = isModsInitialized ? component.mods : {...declMods}; + linkSharedMods(); + linkModsProp(); + linkAttrMods(); + linExpMods(); - for (let i = 0; i < parentMods.length; i++) { - const [modName, getModValue] = parentMods[i]; + return initMods(); - const modVal = getModValue(); + function initMods() { + const modNames = Object.keys(mods); - if (isModsInitialized || modVal != null) { + for (let i = 0; i < modNames.length; i++) { + const modName = modNames[i]; + + const modVal = resolveModVal(mods[modName]); mods[modName] = modVal; + + if (component.hook !== 'beforeDataCreate') { + void component.setMod(modName, modVal); + } } + + return mods; } - if (propMods != null) { - const propNames = Object.keys(propMods); + function linkSharedMods() { + for (let i = 0; i < sharedMods.length; i++) { + const [modName, getModValue] = sharedMods[i]; - for (let i = 0; i < propNames.length; i++) { - const - propName = propNames[i], - propVal = propMods[propNames[i]]; + const modVal = getModValue(); - if (propVal != null || mods[propName] == null) { - mods[propName] = resolveModVal(propVal); + if (isModsInitialized || modVal != null) { + mods[modName] = modVal; } } } - for (let i = 0; i < attrMods.length; i++) { - const [modName, getModValue] = attrMods[i]; + function linkModsProp() { + if (modsProp != null) { + const modNames = Object.keys(modsProp); - const modVal = getModValue(); + for (let i = 0; i < modNames.length; i++) { + const + modName = modNames[i], + modVal = modsProp[modNames[i]]; - if (isModsInitialized || modVal != null) { - mods[modName] = modVal; + if (modVal != null || mods[modName] == null) { + mods[modName] = resolveModVal(modVal); + } + } } } - const {experiments} = component.r.remoteState; + function linkAttrMods() { + for (let i = 0; i < attrMods.length; i++) { + const [modName, getModValue] = attrMods[i]; - if (Object.isArray(experiments)) { - for (let i = 0; i < experiments.length; i++) { - const - exp = experiments[i], - expMods = exp.meta?.mods; + const modVal = getModValue(); - if (!Object.isDictionary(expMods)) { - continue; + if (isModsInitialized || modVal != null) { + mods[modName] = modVal; } + } + } - const expModNames = Object.keys(expMods); + function linExpMods() { + const {experiments} = component.r.remoteState; - for (let i = 0; i < expModNames.length; i++) { + if (Object.isArray(experiments)) { + for (let i = 0; i < experiments.length; i++) { const - modName = expModNames[i], - modVal = expMods[modName]; + exp = experiments[i], + expMods = exp.meta?.mods; - if (modVal != null || mods[modName] == null) { - mods[modName] = resolveModVal(modVal); + if (!Object.isDictionary(expMods)) { + continue; } - } - } - } - const modNames = Object.keys(mods); + const expModNames = Object.keys(expMods); - for (let i = 0; i < modNames.length; i++) { - const modName = modNames[i]; + for (let i = 0; i < expModNames.length; i++) { + const + modName = expModNames[i], + modVal = expMods[modName]; - const modVal = resolveModVal(mods[modName]); - mods[modName] = modVal; - - if (component.hook !== 'beforeDataCreate') { - void component.setMod(modName, modVal); + if (modVal != null || mods[modName] == null) { + mods[modName] = resolveModVal(modVal); + } + } + } } } - - return mods; } function initSharedMods() { - const parent = component.$parent; + const remoteMods: RemoteMods = []; - if (parent == null) { - return; + if (!component.inheritMods) { + return remoteMods; } - const {sharedMods} = parent; + const + parent = component.$parent, + sharedMods = parent?.sharedMods; - if (sharedMods == null) { - return; - } + if (sharedMods != null) { + const modNames = Object.keys(sharedMods); - const modNames = Object.keys(sharedMods); + for (let i = 0; i < modNames.length; i++) { + const modName = modNames[i]; - for (let i = 0; i < modNames.length; i++) { - const modName = modNames[i]; - - component.watch(`$parent.mods.${modName}`, (mods: ModsDict) => { - void component.setMod(modName, mods[modName]); - }); + component.watch(`$parent.mods.${modName}`, (mods: ModsDict) => { + void component.setMod(modName, mods[modName]); + }); - parentMods.push([modName, () => parent.mods[modName]]); + remoteMods.push([modName, () => parent!.mods[modName]]); + } } + + return remoteMods; } - function initModsFromAttrs() { - const attrNames = Object.keys(component.$attrs); + function initAttrMods() { + const + remoteMods: RemoteMods = [], + attrNames = Object.keys(component.$attrs); let el: Nullable; @@ -169,7 +184,7 @@ export function initMods(component: iBlock['unsafe']): ModsDict { void component.setMod(modName, resolveModVal(attrs[attrName])); }); - attrMods.push([modName, () => resolveModVal(component.$attrs[attrName])]); + remoteMods.push([modName, () => resolveModVal(component.$attrs[attrName])]); } } @@ -178,12 +193,14 @@ export function initMods(component: iBlock['unsafe']): ModsDict { el = component.$el; if (el instanceof Element) { - for (let i = 0; i < parentMods.length; i++) { - el.removeAttribute(parentMods[i][0]); + for (let i = 0; i < sharedMods.length; i++) { + el.removeAttribute(sharedMods[i][0]); } } } }); + + return remoteMods; } function resolveModVal(val: unknown) { @@ -198,74 +215,50 @@ export function initMods(component: iBlock['unsafe']): ModsDict { * * @param component * @param oldComponent - * @param name - the field name that is merged when the component is re-created (this will be `mods`) - * @param [link] - the reference name which takes its value based on the current field */ -export function mergeMods( - component: iBlock['unsafe'], - oldComponent: iBlock['unsafe'], - name: string, - link?: string -): void { - if (link == null) { - // @ts-ignore (readonly) - component.mods = {...oldComponent.mods}; - return; - } - - const cache = component.$syncLinkCache.get(link); - - if (cache == null) { - return; - } - - const l = cache[name]; - - if (l == null) { - return; - } +export function mergeMods(component: iBlock['unsafe'], oldComponent: iBlock['unsafe']): void { + const declaredMods = component.meta.component.mods; const - modsProp = getExpandedModsProp(component), - mods = {...oldComponent.mods}; + oldMods = oldComponent.mods, + mergedMods = {...oldMods, ...component.mods}; - if (Object.fastCompare(modsProp, getExpandedModsProp(oldComponent))) { - l.sync(mods); + initModsProp(); + initAttrMods(); - } else { - l.sync(Object.assign(mods, modsProp)); - } + // @ts-ignore (readonly) + component.mods = mergedMods; - function getExpandedModsProp(component: iBlock['unsafe']): ModsDict { - if (link == null) { - return {}; - } + function initAttrMods() { + const attrNames = Object.keys(oldComponent.$attrs); - const modsProp = component[link]; + for (let i = 0; i < attrNames.length; i++) { + const attrName = attrNames[i]; - if (!Object.isDictionary(modsProp)) { - return {}; + if (attrName in declaredMods && component.$attrs[attrName] === oldComponent.$attrs[attrName]) { + mergedMods[attrName] = oldMods[attrName]; + } } + } + function initModsProp() { const - declaredMods = component.meta.component.mods, - expandedModsProp = {...modsProp}; + currentModsProps = component.modsProp, + oldModsProps = oldComponent.modsProp; - const attrNames = Object.keys(component.$attrs); + if (oldModsProps == null || currentModsProps == null || currentModsProps === oldModsProps) { + return; + } - for (let i = 0; i < attrNames.length; i++) { - const attrName = attrNames[i]; + const modNames = Object.keys(oldModsProps); - if (attrName in declaredMods) { - const attr = >component.$attrs[attrName]; + for (let i = 0; i < modNames.length; i++) { + const modName = modNames[i]; - if (attr != null) { - expandedModsProp[attrName] = attr; - } + if (currentModsProps[modName] === oldModsProps[modName]) { + mergedMods[modName] = oldMods[modName]; } } - - return expandedModsProp; } } From f444dfb1fd6cf4c2d049ab4aca401bcc2d8d7201 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 14 Nov 2024 16:21:08 +0300 Subject: [PATCH 330/334] fix: fixed typos --- src/components/super/i-block/modules/mods/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 0e0dbc00e8..8b96d70334 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -43,7 +43,7 @@ export function initMods(component: iBlock['unsafe']): ModsDict { linkSharedMods(); linkModsProp(); linkAttrMods(); - linExpMods(); + linkExpMods(); return initMods(); @@ -104,7 +104,7 @@ export function initMods(component: iBlock['unsafe']): ModsDict { } } - function linExpMods() { + function linkExpMods() { const {experiments} = component.r.remoteState; if (Object.isArray(experiments)) { @@ -193,8 +193,8 @@ export function initMods(component: iBlock['unsafe']): ModsDict { el = component.$el; if (el instanceof Element) { - for (let i = 0; i < sharedMods.length; i++) { - el.removeAttribute(sharedMods[i][0]); + for (let i = 0; i < remoteMods.length; i++) { + el.removeAttribute(remoteMods[i][0]); } } } From d62945971c73bf045c6afa5e4ddc2faf92f0712f Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 14 Nov 2024 16:27:56 +0300 Subject: [PATCH 331/334] chore: better doc --- src/core/component/prop/helpers.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/component/prop/helpers.ts b/src/core/component/prop/helpers.ts index ad119f6c43..dfd8a2e8bf 100644 --- a/src/core/component/prop/helpers.ts +++ b/src/core/component/prop/helpers.ts @@ -40,16 +40,17 @@ export function attachAttrPropsListeners(component: ComponentInterface): void { for (let i = 0; i < attrNames.length; i++) { const + // The name of an attribute can be either the name of a component prop, + // the name of a regular DOM node attribute, + // or an event handler (in which case the attribute name will start with `on`) attrName = attrNames[i], prop = meta.props[attrName]; - if ((prop == null || prop.forceUpdate) && !isPropGetter.test(attrName)) { - continue; - } - - if (prop == null) { + if (prop == null && isPropGetter.test(attrName)) { const propName = isPropGetter.replace(attrName); + // If an accessor is provided for a prop with `forceUpdate: false`, + // it is included in the list of synchronized props if (meta.props[propName]?.forceUpdate === false) { propValuesToUpdate.push([propName, attrName]); From caa43a1ebc317adb639e31db67b84ab195e2821d Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 14 Nov 2024 17:32:24 +0300 Subject: [PATCH 332/334] chore: better doc --- src/components/base/b-virtual-scroll-new/README.md | 2 +- src/components/super/i-block/decorators/README.md | 2 +- src/components/super/i-block/mods/README.md | 4 ++-- src/components/super/i-static-page/CHANGELOG.md | 2 +- src/core/component/decorators/README.md | 2 +- src/core/component/decorators/computed/README.md | 2 +- .../component/decorators/computed/decorator.ts | 2 +- src/core/component/decorators/const.ts | 14 ++++++++++++++ .../decorators/default-value/decorator.ts | 7 +++++++ 9 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/components/base/b-virtual-scroll-new/README.md b/src/components/base/b-virtual-scroll-new/README.md index cd6210d7ab..5e3860a0ad 100644 --- a/src/components/base/b-virtual-scroll-new/README.md +++ b/src/components/base/b-virtual-scroll-new/README.md @@ -591,7 +591,7 @@ Let's also look at another common scenario: . ``` - 2. Implement a global `itemsProcessor` that will add advertisements based on the meta-information. + 2. Implement a global `itemsProcessor` that will add advertisements based on the metainformation. ```typescript import { itemsProcessors } from '@v4fire/client/components/base/b-virtual-scroll-new/const.ts' diff --git a/src/components/super/i-block/decorators/README.md b/src/components/super/i-block/decorators/README.md index e4444da277..8fe706493a 100644 --- a/src/components/super/i-block/decorators/README.md +++ b/src/components/super/i-block/decorators/README.md @@ -8,7 +8,7 @@ This module re-exports the base decorators from `core/component/decorators` and * `@prop` to declare a component input property (aka "prop"); * `@field` to declare a component field; * `@system` to declare a component system field (system field mutations never cause components to re-render); -* `@computed` to attach meta-information to a component computed field or accessor; +* `@computed` to attach metainformation to a component computed field or accessor; * `@hook` to attach a hook listener; * `@watch` to attach a watcher. diff --git a/src/components/super/i-block/mods/README.md b/src/components/super/i-block/mods/README.md index 3a5a8d2fc9..c8cecd7bfe 100644 --- a/src/components/super/i-block/mods/README.md +++ b/src/components/super/i-block/mods/README.md @@ -183,12 +183,12 @@ Or just pass modifiers as regular props. < b-example :visible = true ``` -To disable modifier inheritance, pass the `inheridMods: false` option when creating the component. +To disable modifier inheritance, pass the `inheritMods: false` option when creating the component. ```typescript import iBlock, { component } from 'components/super/i-block/i-block'; -@component({inheridMods: false}) +@component({inheritMods: false}) class bExample extends iBlock {} ``` diff --git a/src/components/super/i-static-page/CHANGELOG.md b/src/components/super/i-static-page/CHANGELOG.md index 3b653b257a..40030552c3 100644 --- a/src/components/super/i-static-page/CHANGELOG.md +++ b/src/components/super/i-static-page/CHANGELOG.md @@ -100,7 +100,7 @@ Changelog #### :rocket: New Feature -* Added the ability to manipulate meta-information of a page +* Added the ability to manipulate metainformation of a page ## v3.29.0 (2022-09-13) diff --git a/src/core/component/decorators/README.md b/src/core/component/decorators/README.md index e3d3856125..77f455fcee 100644 --- a/src/core/component/decorators/README.md +++ b/src/core/component/decorators/README.md @@ -22,6 +22,6 @@ export default class bUser extends iBlock { * `@prop` to declare a component's input property (aka "prop"); * `@field` to declare a component's field; * `@system` to declare a component's system field (system field mutations never cause components to re-render); -* `@computed` to attach meta-information to a component's computed field or accessor; +* `@computed` to attach metainformation to a component's computed field or accessor; * `@hook` to attach a hook listener; * `@watch` to attach a watcher. diff --git a/src/core/component/decorators/computed/README.md b/src/core/component/decorators/computed/README.md index 4905a088de..38112444e0 100644 --- a/src/core/component/decorators/computed/README.md +++ b/src/core/component/decorators/computed/README.md @@ -1,6 +1,6 @@ # core/component/decorators/computed -The decorator assigns meta-information to a computed field or an accessor within a component. +The decorator assigns metainformation to a computed field or an accessor within a component. ```typescript import iBlock, {component, prop, computed} from 'components/super/i-block/i-block'; diff --git a/src/core/component/decorators/computed/decorator.ts b/src/core/component/decorators/computed/decorator.ts index 270e0ee28d..b5ef9763a4 100644 --- a/src/core/component/decorators/computed/decorator.ts +++ b/src/core/component/decorators/computed/decorator.ts @@ -15,7 +15,7 @@ import type { PartDecorator } from 'core/component/decorators/interface'; import type { DecoratorComputed } from 'core/component/decorators/computed/interface'; /** - * Assigns meta-information to a computed field or an accessor within a component + * Assigns metainformation to a computed field or an accessor within a component * * @decorator * @param [params] - an object with accessor parameters diff --git a/src/core/component/decorators/const.ts b/src/core/component/decorators/const.ts index bd7b2f2655..aaf31b5013 100644 --- a/src/core/component/decorators/const.ts +++ b/src/core/component/decorators/const.ts @@ -8,6 +8,20 @@ import type { RegisteredComponent } from 'core/component/decorators/interface'; +// Descriptor of the currently registered DSL component. +// It is initialized for each component file during the project's build phase. +// ```typescript +// import { registeredComponent } from 'core/component/decorators/const'; +// +// import iBlock, { component } from 'components/super/i-block/i-block'; +// +// registeredComponent.name = 'bExample'; +// registeredComponent.layer = '@v4fire/client'; +// registeredComponent.event = 'constructor.b-example.@v4fire/client'; +// +// @component() +// class bExample extends iBlock {} +// ``` export const registeredComponent: RegisteredComponent = { name: undefined, layer: undefined, diff --git a/src/core/component/decorators/default-value/decorator.ts b/src/core/component/decorators/default-value/decorator.ts index ac068e4ced..06e67a8428 100644 --- a/src/core/component/decorators/default-value/decorator.ts +++ b/src/core/component/decorators/default-value/decorator.ts @@ -61,6 +61,13 @@ export function defaultValue(getter: unknown): PartDecorator { regField(key, cluster, params, meta); } else if (isFunction) { + // Registration of methods that are described as properties, such as: + // ``` + // on: typeof this['selfEmitter']['on'] = + // function on(this: iBlockEvent, event: string, handler: Function, opts?: AsyncOptions): object { + // return this.selfEmitter.on(event, handler, opts); + // }; + // ``` Object.defineProperty(meta.constructor.prototype, key, { configurable: true, enumerable: false, From b3c3c9fa60e8c6cba8d03fe5d28d3c52059be30c Mon Sep 17 00:00:00 2001 From: kobezzza Date: Thu, 14 Nov 2024 17:35:39 +0300 Subject: [PATCH 333/334] chore: removed log --- .../set-component-layer/CHANGELOG.md | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 build/ts-transformers/set-component-layer/CHANGELOG.md diff --git a/build/ts-transformers/set-component-layer/CHANGELOG.md b/build/ts-transformers/set-component-layer/CHANGELOG.md deleted file mode 100644 index 3a4087b7be..0000000000 --- a/build/ts-transformers/set-component-layer/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -Changelog -========= - -> **Tags:** -> - :boom: [Breaking Change] -> - :rocket: [New Feature] -> - :bug: [Bug Fix] -> - :memo: [Documentation] -> - :house: [Internal] -> - :nail_care: [Polish] - -## v3.23.5 (2022-07-12) - -#### :rocket: New Feature - -* Initial release From 09966207531232b3f2369ab4bb5877874780ea30 Mon Sep 17 00:00:00 2001 From: kobezzza Date: Fri, 15 Nov 2024 18:56:25 +0300 Subject: [PATCH 334/334] fix: modifiers merging --- .../super/i-block/modules/mods/index.ts | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/components/super/i-block/modules/mods/index.ts b/src/components/super/i-block/modules/mods/index.ts index 8b96d70334..7c22fe7e75 100644 --- a/src/components/super/i-block/modules/mods/index.ts +++ b/src/components/super/i-block/modules/mods/index.ts @@ -217,49 +217,60 @@ export function initMods(component: iBlock['unsafe']): ModsDict { * @param oldComponent */ export function mergeMods(component: iBlock['unsafe'], oldComponent: iBlock['unsafe']): void { - const declaredMods = component.meta.component.mods; - const - oldMods = oldComponent.mods, - mergedMods = {...oldMods, ...component.mods}; + currentModsProps = component.modsProp, + oldModsProps = oldComponent.modsProp; - initModsProp(); - initAttrMods(); + const + currentAttrs = component.$attrs, + oldAttrs = oldComponent.$attrs; - // @ts-ignore (readonly) - component.mods = mergedMods; + const + currentMods = component.mods, + oldMods = oldComponent.mods; - function initAttrMods() { - const attrNames = Object.keys(oldComponent.$attrs); + const + mergedMods = {...currentMods}, + isModsPropsPassed = currentModsProps != null && oldModsProps != null; - for (let i = 0; i < attrNames.length; i++) { - const attrName = attrNames[i]; + const modNames = Object.keys(oldMods); - if (attrName in declaredMods && component.$attrs[attrName] === oldComponent.$attrs[attrName]) { - mergedMods[attrName] = oldMods[attrName]; - } - } - } + for (let i = 0; i < modNames.length; i++) { + const modName = modNames[i]; - function initModsProp() { const - currentModsProps = component.modsProp, - oldModsProps = oldComponent.modsProp; + currentModVal = currentMods[modName], + oldModVal = oldMods[modName]; - if (oldModsProps == null || currentModsProps == null || currentModsProps === oldModsProps) { - return; - } + // True if the modifier value needs to be taken from the old component + let shouldSetFromOld = + currentModVal !== oldModVal && + + // Do not merge modifiers that receive their value through `sync.mod` + component.sync.syncModCache[modName] == null; + + if (shouldSetFromOld) { + // If a modifier was passed through the `modsProp` prop, but the value in the prop has changed + if (isModsPropsPassed && currentModsProps[modName] !== oldMods[modName]) { + shouldSetFromOld = false; - const modNames = Object.keys(oldModsProps); + } else { + const attrName = modName.dasherize(); - for (let i = 0; i < modNames.length; i++) { - const modName = modNames[i]; + // If a modifier was passed through the component's attributes, but its value has changed + if (currentAttrs[attrName] !== oldAttrs[attrName]) { + shouldSetFromOld = false; + } + } - if (currentModsProps[modName] === oldModsProps[modName]) { - mergedMods[modName] = oldMods[modName]; + if (shouldSetFromOld) { + mergedMods[modName] = oldModVal; } } } + + // @ts-ignore (readonly) + component.mods = mergedMods; } /**