diff --git a/.size-limit.json b/.size-limit.json index b063be1..09e2c10 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -5,6 +5,6 @@ }, { "path": "packages/widget-element/dist/index.js", - "limit": "950 B" + "limit": "975 B" } ] diff --git a/packages/widget-element/index.test.ts b/packages/widget-element/index.test.ts index 44817c8..2e14398 100644 --- a/packages/widget-element/index.test.ts +++ b/packages/widget-element/index.test.ts @@ -44,6 +44,40 @@ test('register custom element once', () => { expect(customElements.get('test-widget')).toBe(TestWidget) }) +test('use widget as element', () => { + const widget = document.createElement('test-widget') as TestWidget + + widget.setAttribute('test-id', '123') + + document.body.append(widget) + + expect(document.querySelector('test-widget')).toStrictEqual(widget) + expect(widget.testId).toBe('123') + expect(widget.getAttribute('test-id')).toBe('123') +}) + +test('use widget as constructor', () => { + const widget = new TestWidget() + + widget.testId = '123' + + document.body.append(widget) + + expect(document.querySelector('test-widget')).toStrictEqual(widget) + expect(widget.testId).toBe('123') + expect(widget.getAttribute('test-id')).toBe('123') +}) + +test('use widget as constructor with properties', () => { + const widget = new TestWidget({testId: 123}) + + document.body.append(widget) + + expect(document.querySelector('test-widget')).toStrictEqual(widget) + expect(widget.testId).toBe('123') + expect(widget.getAttribute('test-id')).toBe('123') +}) + test('widget is ready', async () => { const widget = document.createElement('test-widget') as TestWidget const onReady = jest.fn() diff --git a/packages/widget-element/index.ts b/packages/widget-element/index.ts index 757985a..4de6834 100644 --- a/packages/widget-element/index.ts +++ b/packages/widget-element/index.ts @@ -8,7 +8,7 @@ * import {WidgetElement} from '@rambler-tech/widget-element' * import {createApp} from './app' * - * class CustomWidget extends WidgetElement { + * export class CustomWidget extends WidgetElement { * static observedAttributes = ['app-id'] * * async initialize(shadowRoot: ShadowRoot) { @@ -30,9 +30,10 @@ * CustomWidget.register('custom-widget') * ``` * - * Use a widget + * Use a widget as element * ```tsx * import React from 'react' + * import from './components/custom-widget' * * export function Page() { * const widgetRef = useRef() @@ -55,6 +56,36 @@ * ) * } * ``` + * + * Use a widget as constructor + * + * ```tsx + * import React from 'react' + * import {CustomWidget} from './components/custom-widget' + * + * export function Page() { + * const containerRef = useRef() + * + * useEffect(() => { + * const widget = new CustomWidget({appId: '1234'}) + * const onReady = () => { + * // Widget is ready + * } + * widget.addEventListener('ready', onReady) + * containerRef.current.appendChild(widget) + * return () => { + * widget.removeEventListener('ready', onReady) + * containerRef.current.removeChild(widget) + * } + * }, []) + * + * return ( + *
+ *

Hello World

+ *
+ * ) + * } + * ``` */ export class WidgetElement extends HTMLElement { #fallback!: HTMLElement @@ -68,6 +99,30 @@ export class WidgetElement extends HTMLElement { } } + /** Widget custom element constructor */ + constructor(properties: Record = {}) { + super() + + const {observedAttributes} = this.constructor as any + + observedAttributes.forEach((attributeName: string) => { + const name = attributeName.replace(/-(\w)/g, (_, char) => + char.toUpperCase() + ) as keyof this + + Object.defineProperty(this, name, { + get() { + return this.getAttribute(attributeName) + }, + set(value: string) { + this.setAttribute(attributeName, value) + } + }) + }) + + Object.assign(this, properties) + } + async connectedCallback() { this.#shadowRoot = this.#createRoot() this.#fallback = this.#createFallback() @@ -76,9 +131,7 @@ export class WidgetElement extends HTMLElement { this.emit('ready') } - attributeChangedCallback(name: string, oldValue: string, newValue: string) { - this.#updateAttribute(name, oldValue, newValue) - + attributeChangedCallback() { if (this.#shadowRoot) { this.attributeChanged() } @@ -101,20 +154,6 @@ export class WidgetElement extends HTMLElement { return template.content.firstElementChild as HTMLElement } - #updateAttribute(name: string, oldValue: string, newValue: string) { - if (newValue !== oldValue) { - const key = name.replace(/-(\w)/g, (_, char) => - char.toUpperCase() - ) as keyof this - - if (typeof newValue === 'undefined') { - delete this[key] - } else { - this[key] = newValue as any - } - } - } - /** Widget is initialized, and shadow root is attached */ // eslint-disable-next-line no-empty-function initialize(_shadowRoot: ShadowRoot) {}