@e280/sly 0.2.0-2 โ†’ 0.2.0-4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +93 -13
  2. package/package.json +1 -1
  3. package/s/demo/demo.bundle.ts +6 -1
  4. package/s/demo/views/demo.ts +1 -0
  5. package/s/demo/views/incredi.ts +28 -0
  6. package/s/dom/dom.ts +25 -13
  7. package/s/index.html.ts +1 -0
  8. package/s/index.ts +2 -0
  9. package/s/ops/op.ts +2 -2
  10. package/s/views/attributes.ts +6 -6
  11. package/s/views/base-element.ts +84 -0
  12. package/s/views/use.ts +3 -3
  13. package/s/views/view.ts +4 -4
  14. package/x/demo/demo.bundle.js +5 -1
  15. package/x/demo/demo.bundle.js.map +1 -1
  16. package/x/demo/demo.bundle.min.js +17 -14
  17. package/x/demo/demo.bundle.min.js.map +4 -4
  18. package/x/demo/views/demo.js +1 -0
  19. package/x/demo/views/demo.js.map +1 -1
  20. package/x/demo/views/incredi.d.ts +12 -0
  21. package/x/demo/views/incredi.js +21 -0
  22. package/x/demo/views/incredi.js.map +1 -0
  23. package/x/dom/dom.d.ts +6 -5
  24. package/x/dom/dom.js +19 -11
  25. package/x/dom/dom.js.map +1 -1
  26. package/x/index.d.ts +2 -0
  27. package/x/index.html +3 -2
  28. package/x/index.html.js +1 -0
  29. package/x/index.html.js.map +1 -1
  30. package/x/index.js +2 -0
  31. package/x/index.js.map +1 -1
  32. package/x/ops/op.d.ts +2 -2
  33. package/x/ops/op.js +2 -2
  34. package/x/ops/op.js.map +1 -1
  35. package/x/views/attributes.d.ts +1 -1
  36. package/x/views/attributes.js +5 -5
  37. package/x/views/attributes.js.map +1 -1
  38. package/x/views/base-element.d.ts +14 -0
  39. package/x/views/base-element.js +62 -0
  40. package/x/views/base-element.js.map +1 -0
  41. package/x/views/use.d.ts +1 -1
  42. package/x/views/use.js +2 -2
  43. package/x/views/use.js.map +1 -1
  44. package/x/views/view.js +4 -4
  45. package/x/views/view.js.map +1 -1
package/README.md CHANGED
@@ -9,6 +9,7 @@ sly replaces its predecessor, [slate](https://github.com/benevolent-games/slate)
9
9
 
10
10
  - ๐Ÿ‹ **views** โ€” hooks-based, shadow-dom'd, componentizable
11
11
  - ๐Ÿช„ **dom** โ€” the "it's not jquery" multitool
12
+ - ๐Ÿชต **base element** โ€” for a more classical experience
12
13
  - ๐Ÿซ› **ops** โ€” tools for async operations and loading spinners
13
14
  - ๐Ÿงช **testing page** โ€” https://sly.e280.org/
14
15
 
@@ -267,6 +268,85 @@ view(use => () => html`<p>hello world</p>`)
267
268
 
268
269
 
269
270
 
271
+ <br/><br/>
272
+
273
+ ## ๐Ÿฆ๐Ÿชต sly base element
274
+ > *the classic experience*
275
+
276
+ so views are great, and they can be converted into components..
277
+ ..but views have one drawback โ€” you can't actually expose properties on the element instance itself.
278
+
279
+ that's where `BaseElement` comes in.
280
+ it's a proper class-based element, but it lets you have the same `use` hooks that views enjoy.
281
+
282
+ ### ๐Ÿชต base element setup
283
+ - **import stuff**
284
+ ```ts
285
+ import {BaseElement, Use, attributes, dom} from "@e280/sly"
286
+ import {html, css} from "lit"
287
+ ```
288
+ - **declare your element class**
289
+ ```ts
290
+ export class MyElement extends BaseElement {
291
+ static styles = css`span{color:orange}`
292
+
293
+ // custom property
294
+ start = 10
295
+
296
+ // custom attributes
297
+ attrs = attributes(this, {
298
+ multiply: Number
299
+ })
300
+
301
+ // custom methods
302
+ hello() {
303
+ return "world"
304
+ }
305
+
306
+ render(use: Use) {
307
+ const $count = use.signal(1)
308
+ const increment = () => $count.value++
309
+
310
+ const {start} = this
311
+ const {multiply = 1} = this.attrs
312
+ const result = start + (multiply * $count())
313
+
314
+ return html`
315
+ <span>${result}</span>
316
+ <button @click="${increment}">+</button>
317
+ `
318
+ }
319
+ }
320
+ ```
321
+ - **register your element to the dom**
322
+ ```ts
323
+ dom.register({MyElement})
324
+ ```
325
+
326
+ ### ๐Ÿชต base element usage
327
+ - **place the element in your html body**
328
+ ```html
329
+ <body>
330
+ <my-element></my-element>
331
+ </body>
332
+ ```
333
+ - **now you can interact with it**
334
+ ```ts
335
+ const myElement = dom<MyElement>("my-element")
336
+
337
+ // js property
338
+ myElement.start = 100
339
+
340
+ // html attributes
341
+ myElement.attr.multiply = 2
342
+
343
+ // methods
344
+ myElement.hello()
345
+ // "world"
346
+ ```
347
+
348
+
349
+
270
350
  <br/><br/>
271
351
 
272
352
  ## ๐Ÿฆ๐Ÿช„ sly dom
@@ -277,51 +357,51 @@ import {dom} from "@e280/sly"
277
357
  ```
278
358
 
279
359
  ### ๐Ÿช„ dom queries
280
- - require an element
360
+ - `require` an element
281
361
  ```ts
282
362
  dom(".demo")
283
363
  // HTMLElement (or throws)
284
364
  ```
285
- - maybe get an element
365
+ - `maybe` get an element
286
366
  ```ts
287
367
  dom.maybe(".demo")
288
368
  // HTMLElement | undefined
289
369
  ```
290
- - select all elements
370
+ - `select` all elements
291
371
  ```ts
292
372
  dom.all(".demo ul li")
293
373
  // HTMLElement[]
294
374
  ```
295
- - within a specific container
375
+ - `in` the scope of an element
296
376
  ```ts
297
- dom.in(element).require("li")
377
+ dom(element).require("li")
298
378
  // HTMLElement (or throws)
299
379
  ```
300
380
  ```ts
301
- dom.in(element).maybe("li")
381
+ dom(element).maybe("li")
302
382
  // HTMLElement | undefined
303
383
  ```
304
384
  ```ts
305
- dom.in(element).all("li")
385
+ dom(element).all("li")
306
386
  // HTMLElement[]
307
387
  ```
308
388
 
309
389
  ### ๐Ÿช„ dom utilities
310
- - register web components
390
+ - `register` web components
311
391
  ```ts
312
392
  dom.register({MyComponent, AnotherCoolComponent})
313
393
  // <my-component>
314
394
  // <another-cool-component>
315
395
  ```
316
- - render content into an element
396
+ - `render` content into an element
317
397
  ```ts
318
- dom.render(element, html`<p>hello world</p>`)
398
+ dom(element).render(html`<p>hello world</p>`)
319
399
  ```
320
400
  ```ts
321
- dom.in(element).render(html`<p>hello world</p>`)
401
+ dom.in(".demo").render(html`<p>hello world</p>`)
322
402
  ```
323
403
  ```ts
324
- dom.in(".demo").render(html`<p>hello world</p>`)
404
+ dom.render(element, html`<p>hello world</p>`)
325
405
  ```
326
406
 
327
407
 
@@ -383,7 +463,7 @@ import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
383
463
  ```
384
464
  - ๐Ÿ”ฅ create an op that calls and tracks an async fn
385
465
  ```ts
386
- const op = Op.fn(async() => {
466
+ const op = Op.load(async() => {
387
467
  await nap(4000)
388
468
  return 123
389
469
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e280/sly",
3
- "version": "0.2.0-2",
3
+ "version": "0.2.0-4",
4
4
  "description": "web shadow views",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -2,9 +2,14 @@
2
2
  import {dom} from "../dom/dom.js"
3
3
  import {DemoView} from "./views/demo.js"
4
4
  import {CounterView} from "./views/counter.js"
5
+ import {IncrediElement} from "./views/incredi.js"
5
6
 
6
7
  dom.in(".demo").render(DemoView())
7
- dom.register({DemoCounter: CounterView.component(1)})
8
+
9
+ dom.register({
10
+ IncrediElement,
11
+ DemoCounter: CounterView.component(1),
12
+ })
8
13
 
9
14
  console.log("๐Ÿฆ sly")
10
15
 
@@ -19,6 +19,7 @@ const styles = css`
19
19
  :host {
20
20
  display: flex;
21
21
  flex-direction: column;
22
+ align-items: center;
22
23
  gap: 1em;
23
24
  }
24
25
  `
@@ -0,0 +1,28 @@
1
+
2
+ import {css, html} from "lit"
3
+ import {nap, repeat} from "@e280/stz"
4
+
5
+ import {Use} from "../../views/use.js"
6
+ import {attributes} from "../../views/attributes.js"
7
+ import {BaseElement} from "../../views/base-element.js"
8
+
9
+ export class IncrediElement extends BaseElement {
10
+ static styles = css`span{color:orange}`
11
+ attrs = attributes(this, {value: Number})
12
+ something = {whatever: "rofl"}
13
+
14
+ render(use: Use) {
15
+ const {value = 1} = this.attrs
16
+ const $count = use.signal(0)
17
+
18
+ use.mount(() => repeat(async() => {
19
+ await nap(10)
20
+ await $count($count() + 1)
21
+ }))
22
+
23
+ return html`
24
+ <span>${$count() * value}</span>
25
+ `
26
+ }
27
+ }
28
+
package/s/dom/dom.ts CHANGED
@@ -4,20 +4,29 @@ import {register} from "./register.js"
4
4
  import {Content} from "../views/types.js"
5
5
  import {Queryable, Renderable} from "./types.js"
6
6
 
7
+ function require<E extends Element>(
8
+ container: Queryable,
9
+ selector: string,
10
+ ) {
11
+ const e = container.querySelector<E>(selector)
12
+ if (!e) throw new Error(`element not found (${selector})`)
13
+ return e
14
+ }
15
+
7
16
  export class Dom<C extends Queryable> {
8
17
  constructor(public element: C) {}
9
18
 
10
- in<E extends HTMLElement>(elementOrSelector: E | string) {
11
- return new Dom(
12
- typeof elementOrSelector === "string"
13
- ? this.require<E>(elementOrSelector)
14
- : elementOrSelector
19
+ in<E extends HTMLElement>(selectorOrElement: string | E) {
20
+ return new Dom<E>(
21
+ (typeof selectorOrElement === "string")
22
+ ? require(this.element, selectorOrElement) as E
23
+ : selectorOrElement
15
24
  )
16
25
  }
17
26
 
18
27
  require<E extends Element = HTMLElement>(selector: string) {
19
28
  const e = this.element.querySelector<E>(selector)
20
- if (!e) throw new Error(`$1 ${selector} not found`)
29
+ if (!e) throw new Error(`element not found (${selector})`)
21
30
  return e
22
31
  }
23
32
 
@@ -34,18 +43,21 @@ export class Dom<C extends Queryable> {
34
43
  }
35
44
  }
36
45
 
37
- export function dom(selector: string) {
38
- return new Dom(document).require(selector)
46
+ export function dom<E extends Queryable>(selector: string): E
47
+ export function dom<E extends Queryable>(element: E): Dom<E>
48
+ export function dom<E extends Queryable>(selectorOrElement: string | E): E | Dom<E> {
49
+ return (typeof selectorOrElement === "string")
50
+ ? require(document, selectorOrElement) as E
51
+ : new Dom(selectorOrElement)
39
52
  }
40
53
 
41
54
  const doc = new Dom(document)
42
- dom.register = register
43
- dom.render = (container: Renderable, ...content: Content[]) => {
44
- return render(content, container)
45
- }
46
-
47
55
  dom.in = doc.in.bind(doc)
48
56
  dom.require = doc.require.bind(doc)
49
57
  dom.maybe = doc.maybe.bind(doc)
50
58
  dom.all = doc.all.bind(doc)
59
+ dom.register = register
60
+ dom.render = (container: Renderable, ...content: Content[]) => {
61
+ return render(content, container)
62
+ }
51
63
 
package/s/index.html.ts CHANGED
@@ -29,6 +29,7 @@ export default ssg.page(import.meta.url, async orb => ({
29
29
  <h1>sly testing page</h1>
30
30
  <p><a href="https://github.com/e280/sly">github.com/e280/sly</a></p>
31
31
  <p class=lil>v${orb.packageVersion()}</p>
32
+ <incredi-element></incredi-element>
32
33
  <demo-counter>component</demo-counter>
33
34
  <div class=demo></div>
34
35
  `,
package/s/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  export * from "./dom/dashify.js"
3
+ export * from "./dom/dom.js"
3
4
  export * from "./dom/register.js"
4
5
  export * from "./dom/types.js"
5
6
 
@@ -13,6 +14,7 @@ export * from "./ops/types.js"
13
14
  export * as loot from "./loot/index.js"
14
15
 
15
16
  export * from "./views/attributes.js"
17
+ export * from "./views/base-element.js"
16
18
  export * from "./views/css-reset.js"
17
19
  export * from "./views/types.js"
18
20
  export * from "./views/use.js"
package/s/ops/op.ts CHANGED
@@ -16,7 +16,7 @@ export class Op<V> {
16
16
  return op
17
17
  }
18
18
 
19
- static fn<V>(fn: () => Promise<V>) {
19
+ static load<V>(fn: () => Promise<V>) {
20
20
  return this.promise(fn())
21
21
  }
22
22
 
@@ -74,7 +74,7 @@ export class Op<V> {
74
74
  }
75
75
  }
76
76
 
77
- async fn(fn: () => Promise<V>) {
77
+ async load(fn: () => Promise<V>) {
78
78
  return this.promise(fn())
79
79
  }
80
80
 
@@ -26,6 +26,12 @@ export type AttrTypes<A extends AttrSpec> = {
26
26
  [P in keyof A]: AttrType<A[P]>
27
27
  }
28
28
 
29
+ export function onAttrChange(element: HTMLElement, fn: () => void) {
30
+ const observer = new MutationObserver(fn)
31
+ observer.observe(element, {attributes: true})
32
+ return () => observer.disconnect()
33
+ }
34
+
29
35
  export const attributes = <A extends AttrSpec>(
30
36
  element: HTMLElement,
31
37
  spec: A,
@@ -81,9 +87,3 @@ export const attributes = <A extends AttrSpec>(
81
87
 
82
88
  }) as any as AttrTypes<A>
83
89
 
84
- export function onAttrChange(element: HTMLElement, fn: () => void) {
85
- const observer = new MutationObserver(fn)
86
- observer.observe(element, {attributes: true})
87
- return () => observer.disconnect()
88
- }
89
-
@@ -0,0 +1,84 @@
1
+
2
+ import {CSSResultGroup} from "lit"
3
+ import {tracker} from "@e280/strata"
4
+ import {debounce, MapG} from "@e280/stz"
5
+
6
+ import {dom} from "../dom/dom.js"
7
+ import {Content} from "./types.js"
8
+ import {onAttrChange} from "./attributes.js"
9
+ import {applyStyles} from "./utils/apply-styles.js"
10
+ import {Use, _disconnect, _reconnect, _wrap} from "./use.js"
11
+
12
+ export abstract class BaseElement extends HTMLElement {
13
+ static styles: CSSResultGroup | undefined
14
+ readonly shadow: ShadowRoot
15
+
16
+ #use: Use
17
+ #mounts = 0
18
+ #tracking = new MapG<any, () => void>
19
+
20
+ constructor() {
21
+ super()
22
+ this.shadow = this.attachShadow({mode: "open"})
23
+ this.#use = new Use(
24
+ this,
25
+ this.shadow,
26
+ this.updateNow,
27
+ this.update,
28
+ )
29
+ }
30
+
31
+ abstract render(use: Use): Content
32
+
33
+ updateNow = () => {
34
+ this.#use[_wrap](() => {
35
+ const {result, seen} = tracker.observe(() => this.render(this.#use))
36
+
37
+ dom.render(this.shadow, result)
38
+
39
+ for (const item of seen)
40
+ this.#tracking.guarantee(
41
+ item,
42
+ () => tracker.subscribe(item, this.update),
43
+ )
44
+ })
45
+ }
46
+
47
+ update = debounce(0, this.updateNow)
48
+
49
+ connectedCallback() {
50
+ if (this.#mounts === 0) {
51
+ const styles = (this.constructor as any).styles
52
+ if (styles)
53
+ applyStyles(this.shadow, styles)
54
+ this.updateNow()
55
+ }
56
+ else {
57
+ this.#use[_reconnect]()
58
+ }
59
+ this.#attrListening.start()
60
+ this.#mounts++
61
+ }
62
+
63
+ disconnectedCallback() {
64
+ this.#attrListening.stop()
65
+ this.#use[_disconnect]()
66
+ for (const untrack of this.#tracking.values())
67
+ untrack()
68
+ this.#tracking.clear()
69
+ }
70
+
71
+ #attrListening = (() => {
72
+ let stopper: (() => void) | undefined
73
+ const start = () => {
74
+ if (stopper) stopper()
75
+ stopper = onAttrChange(this, () => this.update)
76
+ }
77
+ const stop = () => {
78
+ if (stopper) stopper()
79
+ stopper = undefined
80
+ }
81
+ return {start, stop}
82
+ })()
83
+ }
84
+
package/s/views/use.ts CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  import {CSSResultGroup} from "lit"
3
3
  import {defer, MapG} from "@e280/stz"
4
- import {Derive, Lazy, Signal, signal, SignalOptions} from "@e280/strata/signals"
4
+ import {signal, SignalOptions} from "@e280/strata/signals"
5
5
 
6
6
  import {Op} from "../ops/op.js"
7
7
  import {Mounts} from "./utils/mounts.js"
@@ -94,9 +94,9 @@ export class Use {
94
94
  op = (() => {
95
95
  const that = this
96
96
  function op<V>(f: () => Promise<V>) {
97
- return that.once(() => Op.fn(f))
97
+ return that.once(() => Op.load(f))
98
98
  }
99
- op.fn = op as (<V>(f: () => Promise<V>) => Op<V>)
99
+ op.load = op as (<V>(f: () => Promise<V>) => Op<V>)
100
100
  op.promise = <V>(p: Promise<V>) => this.once(() => Op.promise(p))
101
101
  return op
102
102
  })()
package/s/views/view.ts CHANGED
@@ -46,11 +46,11 @@ function setupView(settings: ViewSettings) {
46
46
  #renderNow() {
47
47
  if (!this.#params) return
48
48
  if (!this.isConnected) return
49
- const {context: w, props} = this.#params
49
+ const {context, props} = this.#params
50
50
 
51
51
  this.#use[_wrap](() => {
52
52
  // apply html attributes
53
- applyAttrs(this.#element, w.attrs)
53
+ applyAttrs(this.#element, context.attrs)
54
54
 
55
55
  // render the template, tracking strata items
56
56
  const {result, seen} = tracker.observe(() => this.#fn(...props))
@@ -67,12 +67,12 @@ function setupView(settings: ViewSettings) {
67
67
 
68
68
  // inject content into light dom
69
69
  if (!situation.isComponent)
70
- render(w.children, this.#element)
70
+ render(context.children, this.#element)
71
71
  })
72
72
  }
73
73
 
74
74
  render(context: ViewContext, props: Props) {
75
- this.#params = {context: context, props}
75
+ this.#params = {context, props}
76
76
  this.#renderNow()
77
77
  return situation.isComponent ? null : this.#element
78
78
  }
@@ -1,7 +1,11 @@
1
1
  import { dom } from "../dom/dom.js";
2
2
  import { DemoView } from "./views/demo.js";
3
3
  import { CounterView } from "./views/counter.js";
4
+ import { IncrediElement } from "./views/incredi.js";
4
5
  dom.in(".demo").render(DemoView());
5
- dom.register({ DemoCounter: CounterView.component(1) });
6
+ dom.register({
7
+ IncrediElement,
8
+ DemoCounter: CounterView.component(1),
9
+ });
6
10
  console.log("๐Ÿฆ sly");
7
11
  //# sourceMappingURL=demo.bundle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"demo.bundle.js","sourceRoot":"","sources":["../../s/demo/demo.bundle.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,eAAe,CAAA;AACjC,OAAO,EAAC,QAAQ,EAAC,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAA;AAE9C,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;AAClC,GAAG,CAAC,QAAQ,CAAC,EAAC,WAAW,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,EAAC,CAAC,CAAA;AAErD,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA"}
1
+ {"version":3,"file":"demo.bundle.js","sourceRoot":"","sources":["../../s/demo/demo.bundle.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,eAAe,CAAA;AACjC,OAAO,EAAC,QAAQ,EAAC,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAA;AAEjD,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;AAElC,GAAG,CAAC,QAAQ,CAAC;IACZ,cAAc;IACd,WAAW,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;CACrC,CAAC,CAAA;AAEF,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA"}