diff --git a/examples/src/main.js b/examples/src/main.js index 68b044a..462f32e 100644 --- a/examples/src/main.js +++ b/examples/src/main.js @@ -1,7 +1,13 @@ import {Component, ProviderComponent, Dispatcher, html} from '../../main.js'; import styles from './style.css'; -class MainDispatcher extends Dispatcher { +class MainComponent extends ProviderComponent { + static get styles() { + return [styles]; + } + get providerId() { + return 'main'; + } init() { return { select: 0, @@ -9,7 +15,7 @@ class MainDispatcher extends Dispatcher { name: 'label' } } - async select(state, i) { + async select({state}, i) { return new Promise((resolve, reject) => { setTimeout(_ => { state.select = i; @@ -18,16 +24,7 @@ class MainDispatcher extends Dispatcher { }, i*1000); }); } -} - -class MainComponent extends ProviderComponent { - static get dispatcher() { - return MainDispatcher; - } - static get styles() { - return [styles]; - } - render(state, dispatch){ + render({state, dispatch}){ return html`
@@ -46,22 +43,16 @@ class MainComponent extends ProviderComponent { } } -class SubDispatcher extends Dispatcher { +class SubComponent extends Component { init() { return { status: 0 }; } -} - -class SubComponent extends Component { - static get dispatcher() { - return SubDispatcher; - } - render(state) { + render({state, $ctx}) { return html` -
${state.$context.sounds[this.dataset.id]}
+
${$ctx('main').state.sounds[this.dataset.id]}
`; } } diff --git a/main.js b/main.js index 7f88977..a0f66c5 100644 --- a/main.js +++ b/main.js @@ -28,105 +28,70 @@ export function html(strings, ...values){ return new RawHTMLTagFuncOutput(strings, values) } -export class Dispatcher extends Function { - constructor(element, store) { - super(); - this.element = element; - this.store = store; - return new Proxy(this, { - apply(target, thisArg, args) { - return target.dispatch(...args); - } - }); - } - async dispatch(action, ...args) { - this.store.update(await this[action](this.store.proxy(), ...args)); - } - init(){ - return {} - } - updatecontext(state){ - return {...state} - } - prop(key, default_value){ - return this.element[key] ? this.element[key] : default_value; - } - attr(key, default_value){ - return this.element.hasAttribute(key) ? this.element.getAttribute(key) : default_value; - } - proxy() { - return new Proxy(this, { - get: (target, prop, receiver) => { - if(prop == '$context'){ - return this.element.context ? this.element.context.dispatcher.proxy() : null; - } - return Reflect.get(target, prop, receiver); - } - }); - } -} - export class Store{ constructor(element, state = {}) { - this.context = null; this.state = state; this.element = element; } - setContext(store) { - this.context = store; - } update(state) { this.state = state; this.element.update(); } - proxy() { - return new Proxy(this.state, { - get: (target, prop, receiver) => { - if(prop == '$context'){ - return this.element.context ? this.element.context.store.proxy() : null; - } - return Reflect.get(target, prop, receiver); - } - }); - } } export class Component extends HTMLElement{ - static get dispatcher() { return Dispatcher; } constructor() { super(); this.store = new Store(this); - this.dispatcher = new this.constructor.dispatcher(this, this.store); + this.context = {}; this.attachShadow({mode: 'open'}); } static get styles() { return []; } async connectedCallback(){ - await this.dispatcher('init'); + await this.dispatch('init'); if(this.afterFirstUpdate) this.afterFirstUpdate(); } + updatecontext(state){ + return {...state} + } + $ctx(contextId) { + return this.context[contextId]; + } setContext(template) { const walker = document.createTreeWalker(template); while(walker.nextNode()){ - walker.currentNode.context = this.context; - if(walker.currentNode.childNodes.length > 0){ - this.setContext(walker.currentNode); - } if(walker.currentNode instanceof Component) { + for(const k of Object.keys(this.context)){ + walker.currentNode.context[k] = this.context[k]; + } walker.currentNode.setContext(walker.currentNode.shadowRoot); } + if(walker.currentNode.childNodes.length > 0){ + this.setContext(walker.currentNode); + } } } - get $contextStore() { - return this.context.store.proxy(); + get state() { + return this.store.state; } - get $contextDispatch() { - return this.context.dispatch.proxy(); + async dispatch(action, ...args) { + this.store.update(await this[action]({ + ...this, + state: this.state, + $ctx: contextId => this.context[contextId], + dispatch: (..._args) => Reflect.apply(this.dispatch, this, args) + }, ...args)); } render(){ return html``; } async update(){ - const {strings, values} = this.render(this.store.proxy(), this.dispatcher.proxy()); + const {strings, values} = this.render({ + ...this, + state: this.state, + $ctx: contextId => this.context[contextId], + dispatch: (...args) => Reflect.apply(this.dispatch, this, args) + }) if(this.template == null) { this.template = new Template(strings); this.shadowRoot.appendChild(this.template.fragment); @@ -150,16 +115,24 @@ export class Component extends HTMLElement{ } export class ProviderComponent extends Component { + get providerId() { + return this.hasAttribute('provider-id') + ? this.getAttribute('provider-id') + : undefined; + } + set providerId(value) { + this.setAttribute('provider-id', value); + } setContext(template) { const walker = document.createTreeWalker(template); while(walker.nextNode()){ - walker.currentNode.context = this; - if(walker.currentNode.childNodes.length > 0){ - this.setContext(walker.currentNode); - } if(walker.currentNode instanceof Component) { + walker.currentNode.context[this.providerId] = this; walker.currentNode.setContext(walker.currentNode.shadowRoot); } + if(walker.currentNode.childNodes.length > 0){ + this.setContext(walker.currentNode); + } } } } @@ -286,7 +259,7 @@ class ValueArray { const walker = document.createTreeWalker(tmp.fragment); while(walker.nextNode()) { if(walker.currentNode instanceof Component) { - walker.currentNode.dispatcher('updatecontext'); + walker.currentNode.dispatch('updatecontext'); } } } diff --git a/omusubi.d.ts b/omusubi.d.ts index 23ec660..5a41610 100644 --- a/omusubi.d.ts +++ b/omusubi.d.ts @@ -9,47 +9,36 @@ declare module 'omusubi-js' { export function defineComponent(tagname: string, options?: ElementDefinitionOptions): (wrapped: typeof Component) => void; export function html(strings: string[], values: string[]): new () => RawHTMLTagFuncOutput; - - export class Dispatcher extends Function { - constructor(element: unknown, store: unknown); - - dispatch(action: string, ...args: string[]): Promise; - - init(): void; - - prop(key: string, default_value: string): string; - - attr(key: string, default_value: string): string; - - updatecontext(state: unknown): object; - - proxy(): object; - } export class Store { constructor(element: unknown, state?: T); - setContext(store: unknown): void; - update(state: T): void; - - proxy(): object; } export class Component extends HTMLElement { - static dispatcher: typeof Dispatcher; static styles: unknown[]; + state: object; + + dispatch(action: string, ...args: unknown[]): Promise; + connectedCallback(): Promise; + + $ctx(contextId: string): ProviderComponent; + updatecontext(state: object): object; + setContext(template: Template): void; - render(state: object, dispatch: unknown): new () => RawHTMLTagFuncOutput; + render(props: object): new () => RawHTMLTagFuncOutput; update(): Promise; } export class ProviderComponent extends Component { + providerId: string; + setContext(template: Template): void; }