Skip to content

Commit

Permalink
feat(widget-element): support using widget element as constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
andrepolischuk committed Sep 9, 2024
1 parent adae397 commit cf7d703
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
},
{
"path": "packages/widget-element/dist/index.js",
"limit": "950 B"
"limit": "975 B"
}
]
34 changes: 34 additions & 0 deletions packages/widget-element/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
77 changes: 58 additions & 19 deletions packages/widget-element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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()
Expand All @@ -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 (
* <div className="page" ref={containerRef}>
* <h1>Hello World</h1>
* </div>
* )
* }
* ```
*/
export class WidgetElement extends HTMLElement {
#fallback!: HTMLElement
Expand All @@ -68,6 +99,30 @@ export class WidgetElement extends HTMLElement {
}
}

/** Widget custom element constructor */
constructor(properties: Record<string, any> = {}) {
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()
Expand All @@ -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()
}
Expand All @@ -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) {}
Expand Down

0 comments on commit cf7d703

Please sign in to comment.