diff --git a/src/modules/esl-popup/core/esl-popup-types.ts b/src/modules/esl-popup/core/esl-popup-types.ts index 538916620..7ae6323e0 100644 --- a/src/modules/esl-popup/core/esl-popup-types.ts +++ b/src/modules/esl-popup/core/esl-popup-types.ts @@ -38,7 +38,7 @@ export interface ESLPopupActionParams extends ESLToggleableActionParams { export type ProxiedParams = Required; /** List of ESLPopupActionParams keys */ -export const KEYSOF_POPUP_ACTION_PARAMS: string[] = [ +export const KEYSOF_POPUP_ACTION_PARAMS: (keyof ESLPopupActionParams)[] = [ 'position', 'positionOrigin', 'behavior', diff --git a/src/modules/esl-popup/core/esl-popup.ts b/src/modules/esl-popup/core/esl-popup.ts index 75e341cd6..4e5d95e2b 100644 --- a/src/modules/esl-popup/core/esl-popup.ts +++ b/src/modules/esl-popup/core/esl-popup.ts @@ -1,12 +1,11 @@ import {range} from '../../esl-utils/misc/array'; import {ExportNs} from '../../esl-utils/environment/export-ns'; -import {bind, memoize, ready, attr, boolAttr, jsonAttr, listen, decorate, prop} from '../../esl-utils/decorators'; +import {bind, memoize, ready, attr, boolAttr, jsonAttr, listen, decorate} from '../../esl-utils/decorators'; import {ESLTraversingQuery} from '../../esl-traversing-query/core'; import {afterNextRender, rafDecorator} from '../../esl-utils/async/raf'; import {ESLToggleable} from '../../esl-toggleable/core'; import {isElement, isRelativeNode, isRTL, Rect, getListScrollParents, getViewportRect} from '../../esl-utils/dom'; import {parseBoolean, parseNumber, toBooleanAttribute} from '../../esl-utils/misc/format'; -import {copyDefinedKeys} from '../../esl-utils/misc/object'; import {ESLIntersectionTarget, ESLIntersectionEvent} from '../../esl-event-listener/core/targets/intersection.target'; import {calcPopupPosition, isOnHorizontalAxis} from './esl-popup-position'; import {ESLPopupPlaceholder} from './esl-popup-placeholder'; @@ -19,26 +18,6 @@ import type {ESLPopupActionParams, ProxiedParams} from './esl-popup-types'; const INTERSECTION_LIMIT_FOR_ADJACENT_AXIS = 0.7; const DEFAULT_OFFSET_ARROW = 50; -function buildConfig(params: ESLPopupActionParams, popup: ESLPopup): ProxiedParams { - return new Proxy(params as ProxiedParams, { - get(target: ESLPopupActionParams, p: T | symbol): ESLPopupActionParams[T] { - return Reflect.get(target, p) ?? Reflect.get(popup, p); - }, - set: (): boolean => false, - deleteProperty: (): boolean => false, - has(target: ESLPopupActionParams, p: T | symbol): boolean { - return Reflect.has(target, p) || Reflect.has(popup, p); - }, - ownKeys(target: ESLPopupActionParams): (string | symbol)[] { - const paramKeys = Reflect.ownKeys(target); - const popupKeys = popup.PARAM_KEYS - .filter((key: string) => !paramKeys.includes(key) && Reflect.has(popup, key as keyof ESLPopup)); - return [...paramKeys, ...popupKeys]; - }, - getOwnPropertyDescriptor: (): PropertyDescriptor | undefined => ({enumerable: true, configurable: true}) - }); -} - @ExportNs('Popup') export class ESLPopup extends ESLToggleable { public static override is = 'esl-popup'; @@ -49,8 +28,7 @@ export class ESLPopup extends ESLToggleable { intersectionMargin: '0px' }; - /** List of popup params keys */ - @prop(KEYSOF_POPUP_ACTION_PARAMS) public PARAM_KEYS: string[]; + public static PARAM_KEYS: string[] = KEYSOF_POPUP_ACTION_PARAMS as string[]; /** Classname of popups arrow element */ @attr({defaultValue: 'esl-popup-arrow'}) public arrowClass: string; @@ -103,14 +81,31 @@ export class ESLPopup extends ESLToggleable { public override a11y: ESLA11yType; public $placeholder: ESLPopupPlaceholder | null; - public config: ProxiedParams; - /** Store params and create proxied config */ - protected set params(value: ESLPopupActionParams) { - this._params = copyDefinedKeys(value); - this.config = buildConfig(this._params, this); + @memoize() + public get config(): ProxiedParams { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const $popup = this; + return new Proxy({} as ProxiedParams, { + get(target: ESLPopupActionParams, p: T | symbol): ESLPopupActionParams[T] { + return Reflect.get($popup._params, p) ?? Reflect.get($popup, p); + }, + set: (): boolean => false, + deleteProperty: (): boolean => false, + has(target: ESLPopupActionParams, p: T | symbol): boolean { + return Reflect.has($popup._params, p) || Reflect.has($popup, p); + }, + ownKeys(target: ESLPopupActionParams): (string | symbol)[] { + const paramKeys = Reflect.ownKeys($popup._params); + const popupKeys = ($popup.constructor as typeof ESLPopup).PARAM_KEYS + .filter((key: string) => !paramKeys.includes(key) && Reflect.has($popup, key as keyof ESLPopup)); + return [...paramKeys, ...popupKeys]; + }, + getOwnPropertyDescriptor: (): PropertyDescriptor | undefined => ({enumerable: true, configurable: true}) + }); } - protected _params: ESLPopupActionParams; + + protected _params: ESLPopupActionParams = {}; protected _intersectionRatio: IntersectionRatioRect = {}; protected _updateLoopID: number; @@ -133,11 +128,6 @@ export class ESLPopup extends ESLToggleable { return Rect.from(this.$container).shift(window.scrollX, window.scrollY); } - public constructor() { - super(); - this.params = {}; - } - @ready protected override connectedCallback(): void { super.connectedCallback(); @@ -200,7 +190,7 @@ export class ESLPopup extends ESLToggleable { } super.onShow(params); - this.params = params; + this._params = params; this.style.visibility = 'hidden'; // eliminates the blinking of the popup at the previous position diff --git a/src/modules/esl-popup/test/esl-popup.config.test.ts b/src/modules/esl-popup/test/esl-popup.config.test.ts index b94f5706b..5d57040d0 100644 --- a/src/modules/esl-popup/test/esl-popup.config.test.ts +++ b/src/modules/esl-popup/test/esl-popup.config.test.ts @@ -17,7 +17,11 @@ describe('ESLPopup: proxy logic of config', () => { } TestPopup2.register(); - const defaultAttributes: ESLPopupActionParams = { + const defaultAction: ESLPopupActionParams = { + action: 'show' + }; + + const defaultAttrs: ESLPopupActionParams = { position: 'top', positionOrigin: 'outer', behavior: 'fit', @@ -43,12 +47,12 @@ describe('ESLPopup: proxy logic of config', () => { test('should return default attributes if the showing parameters and default parameters are missing', () => { $popup = new TestPopup1(); $popup.show({}); - expect({...$popup.config}).toEqual(defaultAttributes); + expect({...$popup.config}).toEqual({...defaultAttrs, ...defaultAction}); }); test('should return merging default parameters and default attributes if the showing parameters are missing', () => { $popup.show({}); - expect({...$popup.config}).toEqual({...defaultAttributes, ...defaultParams}); + expect({...$popup.config}).toEqual({...defaultAttrs, ...defaultParams, ...defaultAction}); }); test('should return merging showing parameters, default parameters, and default attributes', () => { @@ -56,7 +60,7 @@ describe('ESLPopup: proxy logic of config', () => { extraClass: 'test-class' }; $popup.show(params); - expect({...$popup.config}).toEqual({...defaultAttributes, ...defaultParams, ...params}); + expect({...$popup.config}).toEqual({...defaultAttrs, ...defaultParams, ...params, ...defaultAction}); }); test('should have value from default params in the case also prop defined via attributes', () => { @@ -90,7 +94,7 @@ describe('ESLPopup: proxy logic of config', () => { const entries = Object.entries($popup.config); expect(entries).toEqual(expect.arrayContaining([ ...Object.entries(defaultParams), - ...Object.entries(defaultAttributes) + ...Object.entries(defaultAttrs) ])); }); @@ -102,9 +106,19 @@ describe('ESLPopup: proxy logic of config', () => { extraStyle: 'color: red' }; $popup.show(params1); - expect({...$popup.config}).toEqual({...defaultAttributes, ...defaultParams, ...params1}); + expect({...$popup.config}).toEqual({...defaultAttrs, ...defaultParams, ...params1, ...defaultAction}); $popup.hide(); $popup.show(params2); - expect({...$popup.config}).toEqual({...defaultAttributes, ...defaultParams, ...params2}); + expect({...$popup.config}).toEqual({...defaultAttrs, ...defaultParams, ...params2, ...defaultAction}); + }); + + test('should be updated after attribure changing', () => { + const params: ESLPopupActionParams = { + extraClass: 'test-class' + }; + $popup.show(params); + expect({...$popup.config}).toEqual({...defaultAttrs, ...defaultParams, ...params, ...defaultAction}); + $popup.container = '::prev'; + expect({...$popup.config}).toEqual({...defaultAttrs, container: '::prev', ...defaultParams, ...params, ...defaultAction}); }); }); diff --git a/src/modules/esl-share/core/esl-share-popup.ts b/src/modules/esl-share/core/esl-share-popup.ts index 7a7f7e1ad..9d717270c 100644 --- a/src/modules/esl-share/core/esl-share-popup.ts +++ b/src/modules/esl-share/core/esl-share-popup.ts @@ -1,6 +1,6 @@ import {ExportNs} from '../../esl-utils/environment/export-ns'; import {ESLPopup, KEYSOF_POPUP_ACTION_PARAMS} from '../../esl-popup/core'; -import {bind, listen, memoize, prop} from '../../esl-utils/decorators'; +import {bind, listen, memoize} from '../../esl-utils/decorators'; import {ESLShareButton} from './esl-share-button'; import {ESLShareConfig} from './esl-share-config'; @@ -24,7 +24,7 @@ export interface ESLSharePopupActionParams extends ESLPopupActionParams { disableArrow?: boolean; } /** List of ESLSharePopupActionParams keys */ -export const KEYSOF_SHAREPOPUP_ACTION_PARAMS: string[] = [ +export const KEYSOF_SHAREPOPUP_ACTION_PARAMS: (keyof ESLSharePopupActionParams)[] = [ ...KEYSOF_POPUP_ACTION_PARAMS, 'list', 'dir', @@ -52,6 +52,8 @@ export class ESLSharePopup extends ESLPopup { hideDelay: 300 }; + public static override PARAM_KEYS: string[] = KEYSOF_SHAREPOPUP_ACTION_PARAMS as string[]; + /** List of attributes to forward from the activator to the {@link ESLSharePopup} */ public static forwardedAttrs = ['share-title', 'share-url']; @@ -67,9 +69,6 @@ export class ESLSharePopup extends ESLPopup { return ESLSharePopup.create(); } - /** List of share popup params keys */ - @prop(KEYSOF_SHAREPOPUP_ACTION_PARAMS) public override PARAM_KEYS: string[]; - /** Hashstring with a list of buttons already rendered in the popup */ protected _list: string = ''; diff --git a/src/modules/esl-tooltip/core/esl-tooltip.ts b/src/modules/esl-tooltip/core/esl-tooltip.ts index 4dd71aab4..02ebad258 100644 --- a/src/modules/esl-tooltip/core/esl-tooltip.ts +++ b/src/modules/esl-tooltip/core/esl-tooltip.ts @@ -1,6 +1,6 @@ import {ExportNs} from '../../esl-utils/environment/export-ns'; import {ESLPopup, KEYSOF_POPUP_ACTION_PARAMS} from '../../esl-popup/core'; -import {memoize, prop} from '../../esl-utils/decorators'; +import {memoize} from '../../esl-utils/decorators'; import type {ESLPopupActionParams} from '../../esl-popup/core'; @@ -17,7 +17,7 @@ export interface ESLTooltipActionParams extends ESLPopupActionParams { disableArrow?: boolean; } /** List of ESLTooltipActionParams keys */ -export const KEYSOF_TOOLTIP_ACTION_PARAMS: string[] = [ +export const KEYSOF_TOOLTIP_ACTION_PARAMS: (keyof ESLTooltipActionParams)[] = [ ...KEYSOF_POPUP_ACTION_PARAMS, 'text', 'html', @@ -36,6 +36,8 @@ export class ESLTooltip extends ESLPopup { hideDelay: 300 }; + public static override PARAM_KEYS: string[] = KEYSOF_TOOLTIP_ACTION_PARAMS as string[]; + /** Shared instanse of Tooltip */ @memoize() public static get sharedInstance(): ESLTooltip { @@ -57,9 +59,6 @@ export class ESLTooltip extends ESLPopup { this.sharedInstance.hide(params); } - /** List of tooltip params keys */ - @prop(KEYSOF_TOOLTIP_ACTION_PARAMS) public override PARAM_KEYS: string[]; - public override connectedCallback(): void { super.connectedCallback(); this.classList.add(ESLPopup.is);