Skip to content

Commit

Permalink
[add] HTML attributes & DOM properties sync with Modern Decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
TechQuery committed Jan 5, 2024
1 parent 04c45b7 commit 79817cf
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 7 deletions.
4 changes: 3 additions & 1 deletion Migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ export interface MyTagProps extends WebCellProps {
})
@observer
-export class MyTag extends mixin<MyTagProps>() {
+export class MyTag extends WebCell<MyTagProps>() {
+export class MyTag extends HTMLElement {
+ declare props: MyTagProps;

@attribute
- @watch
+ @observable
Expand Down
4 changes: 3 additions & 1 deletion ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ class State {
mode: 'open'
})
@observer
export class TestTag extends WebCell<TestTagProps>() {
export class TestTag extends HTMLElement {
declare props: TestTagProps;

@attribute
@observable
topic = 'Test';
Expand Down
68 changes: 63 additions & 5 deletions source/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { DOMRenderer, DataObject, VNode } from 'dom-renderer';
import { autorun } from 'mobx';
import { CustomElement, isHTMLElementClass } from 'web-utility';
import {
CustomElement,
isHTMLElementClass,
parseJSON,
toCamelCase,
toHyphenCase
} from 'web-utility';

export interface ComponentMeta
extends ElementDefinitionOptions,
Expand All @@ -9,12 +15,12 @@ export interface ComponentMeta
}

/**
* Class decorator of Web components
* `class` decorator of Web components
*/
export function component({ tagName, ...meta }: ComponentMeta) {
return <T extends CustomElementConstructor>(
Class: T,
{ addInitializer }: ClassDecoratorContext
{ addInitializer }: ClassDecoratorContext<CustomElementConstructor>
) => {
class RendererComponent extends (Class as CustomElementConstructor) {
protected internals = this.attachInternals();
Expand Down Expand Up @@ -42,7 +48,7 @@ export function component({ tagName, ...meta }: ComponentMeta) {
declare render: () => VNode;
}

addInitializer(function (this: CustomElementConstructor) {
addInitializer(function () {
globalThis.customElements?.define(tagName, this, meta);
});
return RendererComponent as unknown as T;
Expand Down Expand Up @@ -86,10 +92,39 @@ function wrapClass<T extends ClassComponent>(Class: T) {
// @ts-ignore
this.update = () =>
this.disposers.push(autorun(() => update.call(this)));

const names: string[] = this.constructor['observedAttributes'];

this.disposers.push(
...names.map(name => autorun(() => this.syncPropAttr(name)))
);
}

disconnectedCallback() {
for (const disposer of this.disposers) disposer();

this.disposers.length = 0;
}

attributeChangedCallback(name: string, old: string, value: string) {
this[toCamelCase(name)] = parseJSON(value);

super['attributeChangedCallback']?.(name, old, value);
}

syncPropAttr(name: string) {
const value = this[toCamelCase(name)];

if (value != null && value !== false)
super.setAttribute(
name,
value === true
? name
: typeof value !== 'object'
? value
: JSON.stringify(value)
);
else this.removeAttribute(name);
}
}
return ObserverComponent as unknown as T;
Expand All @@ -98,7 +133,7 @@ function wrapClass<T extends ClassComponent>(Class: T) {
export type WebCellComponent = FunctionComponent | ClassComponent;

/**
* Class decorator of Web components for MobX
* `class` decorator of Web components for MobX
*/
export function observer<T extends WebCellComponent>(
func: T,
Expand All @@ -112,6 +147,29 @@ export function observer<T extends WebCellComponent>(
return isHTMLElementClass(func) ? wrapClass(func) : wrapFunction(func);
}

/**
* `accessor` decorator of MobX `@observable` for HTML attributes
*/
export function attribute<C extends CustomElementConstructor, V>(
_: ClassAccessorDecoratorTarget<C, V>,
{ name, addInitializer }: ClassAccessorDecoratorContext<CustomElement>
) {
addInitializer(function () {
const { constructor } = this;
var names = constructor['observedAttributes'];

if (!names) {
names = [];

Object.defineProperty(constructor, 'observedAttributes', {
configurable: true,
get: () => names
});
}
names.push(toHyphenCase(name.toString()));
});
}

declare global {
namespace JSX {
interface ElementAttributesProperty {
Expand Down

1 comment on commit 79817cf

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deploy preview for web-cell ready!

✅ Preview
https://web-cell-bcscbhwp2-techquery.vercel.app

Built with commit 79817cf.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.