Skip to content

Commit aa3422f

Browse files
committed
Continued work on context support
1 parent 621521f commit aa3422f

File tree

2 files changed

+80
-83
lines changed

2 files changed

+80
-83
lines changed

src/main/js-element.ts

Lines changed: 53 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
// === exports =======================================================
22

33
// functions and singletons
4-
export {
5-
component,
6-
createCtx,
7-
defineProvider,
8-
elem,
9-
intercept,
10-
prop,
11-
setMethods,
12-
Attrs
13-
}
4+
export { component, createCtx, elem, intercept, prop, setMethods, Attrs }
145

156
// types
167
export { Context, Ctrl, MethodsOf }
@@ -76,10 +67,10 @@ type Ctrl = {
7667

7768
type InterceptFn = (ctrl: Ctrl, next: () => void) => void
7869

79-
type Context<T> = {
70+
type Context<T> = Readonly<{
8071
kind: 'context'
8172
defaultValue: T
82-
}
73+
}>
8374

8475
// === decorators (all public) =======================================
8576

@@ -91,10 +82,29 @@ function elem<E extends Component, C>(params: {
9182
}
9283
styles?: string | string[] | (() => string | string[])
9384
uses?: any[]
94-
}) {
85+
}): (clazz: new () => E) => void
86+
87+
function elem<T, E extends Component & { value?: T }>(params: {
88+
tag: `${string}-provider`
89+
ctx: Context<T>
90+
}): (clazz: new () => E) => void
91+
92+
function elem<E extends Component>(params: any) {
9593
return (clazz: new () => E): void => {
9694
definePropValue(clazz, 'tagName', params.tag)
9795

96+
if (params.ctx) {
97+
const ctx = params.ctx
98+
console.log(params)
99+
params = {
100+
tag: params.tag,
101+
impl: {
102+
init: (self: E, ctrl: Ctrl) => initProvider(self, ctrl, ctx),
103+
patch: () => {}
104+
}
105+
}
106+
}
107+
98108
let elemConfig = elemConfigByClass.get(clazz)
99109

100110
if (!elemConfig) {
@@ -188,9 +198,10 @@ class BaseElement extends HTMLElement {
188198

189199
constructor() {
190200
super()
191-
const { init, patch } = elemConfigByClass.get(this.constructor)!.impl
192201

193-
let styles = elemConfigByClass.get(this.constructor)!.styles
202+
const elemConfig = elemConfigByClass.get(this.constructor)!
203+
const { init, patch } = elemConfig.impl
204+
let styles = elemConfig.styles
194205

195206
if (typeof styles !== 'string') {
196207
styles = typeof styles === 'function' ? styles() : styles
@@ -207,6 +218,7 @@ class BaseElement extends HTMLElement {
207218
}
208219

209220
const stylesElement = document.createElement('span')
221+
stylesElement.setAttribute('data-role', 'styles')
210222

211223
if (styles) {
212224
const styleElem = document.createElement('style')
@@ -215,8 +227,9 @@ class BaseElement extends HTMLElement {
215227
}
216228

217229
const contentElement = document.createElement('span')
230+
contentElement.setAttribute('data-role', 'content')
218231
this.attachShadow({ mode: 'open' })
219-
contentElement.append(document.createElement('span'))
232+
//contentElement.append(document.createElement('span'))
220233
this.shadowRoot!.append(stylesElement, contentElement)
221234

222235
let initialized = false
@@ -510,64 +523,39 @@ function createCtx<T>(defaultValue?: T): Context<T> {
510523
})
511524
}
512525

513-
function defineProvider<T>(
514-
tagName: string,
515-
ctx: Context<T>
516-
): { new (): HTMLElement & { value: T | undefined } } {
517-
const eventName = `$$context$$`
518-
519-
class CtxProviderElement extends HTMLElement {
520-
private __value?: T = undefined
521-
private __subscribers: ((value: T) => void)[] = []
522-
private __cleanup: (() => void) | null = null
526+
function initProvider(self: any, ctrl: Ctrl, ctx: Context<any>): () => null {
527+
const subscribers = new Set<any>() // TODO
528+
let cleanup: any = null // TODO
529+
let value = ctx.defaultValue
523530

524-
constructor() {
525-
super()
526-
this.attachShadow({ mode: 'open' })
531+
const eventListener = (ev: any) => {
532+
if (ev.detail.context !== ctx) {
533+
return
527534
}
528535

529-
get value(): T | undefined {
530-
return this.__value
531-
}
532-
533-
set value(val: T | undefined) {
534-
if (val !== this.__value) {
535-
this.__value = val
536-
this.__subscribers.forEach((subscriber) => subscriber(val!))
537-
}
538-
}
539-
540-
connectedCallback() {
541-
this.shadowRoot!.innerHTML = '<slot></slot>'
536+
const callback = ev.detail.callback
542537

543-
const eventListener = (ev: any) => {
544-
if (ev.detail.context !== ctx) {
545-
return
546-
}
538+
ev.stopPropagation()
539+
subscribers.add(callback)
547540

548-
ev.stopPropagation()
549-
this.__subscribers.push(ev.detail.callback)
541+
ev.detail.cancelled.then(() => {
542+
subscribers.delete(callback)
543+
})
544+
}
550545

551-
ev.detail.cancelled.then(() => {
552-
this.__subscribers.splice(
553-
this.__subscribers.indexOf(ev.detail.callback),
554-
1
555-
)
556-
})
557-
}
546+
self.addEventListener('$$context$$', eventListener)
558547

559-
this.addEventListener(eventName, eventListener)
560-
this.__cleanup = () => this.removeEventListener(eventName, eventListener)
561-
}
548+
cleanup = () => self.removeEventListener('$$context$$', eventListener)
562549

563-
disconnectCallback() {
564-
this.__subscribers.length === 0
565-
this.__cleanup!()
566-
this.__cleanup = null
550+
ctrl.afterUpdate(() => {
551+
if (self.value !== value) {
552+
value = self.value
553+
subscribers.forEach((subscriber) => subscriber(value))
567554
}
568-
}
555+
})
569556

570-
registerElement(tagName, CtxProviderElement)
557+
const contentContainer = self.shadowRoot.lastChild
558+
contentContainer.appendChild(document.createElement('slot'))
571559

572-
return CtxProviderElement
560+
return () => null
573561
}

src/stories/demos/context-demo.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
1-
import { component, createCtx, defineProvider, elem, prop } from 'js-element'
1+
import { component, createCtx, elem, prop, Attrs } from 'js-element'
22
import { useCtx, useInterval, useState } from 'js-element/hooks'
33
import { html, withLit } from 'js-element/lit'
44

5-
const ThemeCtx = createCtx('light')
6-
const ThemeProvider = defineProvider('theme-provider', ThemeCtx)
5+
const themeCtx = createCtx('light')
76

87
@elem({
9-
tag: 'contxt-demo',
8+
tag: 'theme-provider',
9+
ctx: themeCtx
10+
})
11+
class ThemeProvider extends component() {
12+
@prop({ attr: Attrs.string })
13+
value?: string
14+
}
15+
16+
@elem({
17+
tag: 'theme-info',
18+
impl: withLit(implThemeInfo)
19+
})
20+
class ThemeInfo extends component() {}
21+
22+
function implThemeInfo() {
23+
const ctx = useCtx({ theme: themeCtx })
24+
25+
return () => html`<div>Current theme: ${ctx.theme}</div>`
26+
}
27+
28+
@elem({
29+
tag: 'context-demo',
30+
uses: [ThemeProvider, ThemeInfo],
1031
impl: withLit(implContextDemo)
1132
})
1233
class ContextDemo extends component() {}
@@ -22,23 +43,11 @@ function implContextDemo(self: ContextDemo) {
2243
<div>
2344
<b>Value for theme will change every second:</b>
2445
<br />
25-
<theme-provider .value=${state.theme}>
26-
<theme-info />
46+
<theme-provider value=${state.theme}>
47+
<theme-info></theme-info>
2748
</theme-provider>
2849
</div>
2950
`
3051
}
3152

32-
@elem({
33-
tag: 'theme-info',
34-
impl: withLit(implThemeInfo)
35-
})
36-
class ThemeInfo extends component() {}
37-
38-
function implThemeInfo() {
39-
const ctx = useCtx({ theme: ThemeCtx })
40-
41-
return () => html`<div>Current theme: ${ctx.theme}</div>`
42-
}
43-
4453
export default ContextDemo

0 commit comments

Comments
 (0)