@e280/sly 0.2.4 → 0.3.0-1

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 (256) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +302 -614
  3. package/package.json +6 -8
  4. package/s/_archive/README.md +1221 -0
  5. package/s/{base → _archive/base}/element.ts +5 -2
  6. package/s/_archive/view/index.ts +7 -0
  7. package/s/_archive/view/types.ts +45 -0
  8. package/s/{view → _archive/view}/utils/parts/capsule.ts +9 -2
  9. package/s/demo/demo.bundle.ts +2 -9
  10. package/s/demo/views/counter-light.ts +13 -0
  11. package/s/demo/views/counter-shadow.ts +16 -0
  12. package/s/demo/views/demo.ts +24 -18
  13. package/s/demo/views/loaders.ts +7 -7
  14. package/s/index.html.ts +30 -33
  15. package/s/index.ts +0 -2
  16. package/s/loaders/make.ts +1 -1
  17. package/s/loaders/parts/ascii-anim.ts +6 -8
  18. package/s/loaders/parts/error-display.ts +9 -9
  19. package/s/tests.test.ts +1 -4
  20. package/s/view/common/css-reset.ts +19 -0
  21. package/s/view/hooks/plumbing/hooks.ts +28 -0
  22. package/s/view/hooks/plumbing/hookscope.ts +12 -0
  23. package/s/view/hooks/use-css.ts +14 -0
  24. package/s/view/hooks/use-cx.ts +41 -0
  25. package/s/view/hooks/use-life.ts +17 -0
  26. package/s/view/hooks/use-mount.ts +30 -0
  27. package/s/view/hooks/use-name.ts +10 -0
  28. package/s/view/hooks/use-once.ts +9 -0
  29. package/s/view/hooks/use-op.ts +12 -0
  30. package/s/view/hooks/use-ref.ts +11 -0
  31. package/s/view/hooks/use-signal.ts +16 -0
  32. package/s/view/hooks/use-state.ts +20 -0
  33. package/s/view/hooks/use-wake.ts +8 -0
  34. package/s/view/index.ts +17 -4
  35. package/s/view/light.ts +50 -0
  36. package/s/view/parts/apply-attrs.ts +22 -0
  37. package/s/view/parts/apply-styles.ts +21 -0
  38. package/s/view/parts/cx.ts +26 -0
  39. package/s/view/parts/reactivity.ts +22 -0
  40. package/s/view/parts/sly-shadow.ts +8 -0
  41. package/s/view/shadow.ts +93 -0
  42. package/s/view/types.ts +15 -34
  43. package/x/demo/demo.bundle.js +2 -8
  44. package/x/demo/demo.bundle.js.map +1 -1
  45. package/x/demo/demo.bundle.min.js +45 -58
  46. package/x/demo/demo.bundle.min.js.map +4 -4
  47. package/x/demo/views/counter-light.d.ts +1 -0
  48. package/x/demo/views/counter-light.js +10 -0
  49. package/x/demo/views/counter-light.js.map +1 -0
  50. package/x/demo/views/counter-shadow.d.ts +1 -0
  51. package/x/demo/views/counter-shadow.js +12 -0
  52. package/x/demo/views/counter-shadow.js.map +1 -0
  53. package/x/demo/views/demo.d.ts +1 -4
  54. package/x/demo/views/demo.js +23 -18
  55. package/x/demo/views/demo.js.map +1 -1
  56. package/x/demo/views/loaders.d.ts +1 -1
  57. package/x/demo/views/loaders.js +7 -7
  58. package/x/demo/views/loaders.js.map +1 -1
  59. package/x/index.d.ts +0 -2
  60. package/x/index.html +30 -140
  61. package/x/index.html.js +31 -31
  62. package/x/index.html.js.map +1 -1
  63. package/x/index.js +0 -2
  64. package/x/index.js.map +1 -1
  65. package/x/loaders/make.d.ts +1 -1
  66. package/x/loaders/parts/ascii-anim.d.ts +1 -1
  67. package/x/loaders/parts/ascii-anim.js +6 -7
  68. package/x/loaders/parts/ascii-anim.js.map +1 -1
  69. package/x/loaders/parts/error-display.d.ts +1 -1
  70. package/x/loaders/parts/error-display.js +9 -9
  71. package/x/loaders/parts/error-display.js.map +1 -1
  72. package/x/tests.test.js +1 -4
  73. package/x/tests.test.js.map +1 -1
  74. package/x/view/common/css-reset.js +17 -0
  75. package/x/view/common/css-reset.js.map +1 -0
  76. package/x/view/hooks/plumbing/hooks.d.ts +11 -0
  77. package/x/view/hooks/plumbing/hooks.js +26 -0
  78. package/x/view/hooks/plumbing/hooks.js.map +1 -0
  79. package/x/view/hooks/plumbing/hookscope.d.ts +10 -0
  80. package/x/view/hooks/plumbing/hookscope.js +12 -0
  81. package/x/view/hooks/plumbing/hookscope.js.map +1 -0
  82. package/x/view/hooks/use-css.d.ts +4 -0
  83. package/x/view/hooks/use-css.js +10 -0
  84. package/x/view/hooks/use-css.js.map +1 -0
  85. package/x/view/hooks/use-cx.d.ts +10 -0
  86. package/x/view/hooks/use-cx.js +33 -0
  87. package/x/view/hooks/use-cx.js.map +1 -0
  88. package/x/view/hooks/use-life.d.ts +2 -0
  89. package/x/view/hooks/use-life.js +13 -0
  90. package/x/view/hooks/use-life.js.map +1 -0
  91. package/x/{base/utils/mounts.d.ts → view/hooks/use-mount.d.ts} +1 -0
  92. package/x/{base/utils/mounts.js → view/hooks/use-mount.js} +7 -1
  93. package/x/view/hooks/use-mount.js.map +1 -0
  94. package/x/view/hooks/use-name.d.ts +2 -0
  95. package/x/view/hooks/use-name.js +8 -0
  96. package/x/view/hooks/use-name.js.map +1 -0
  97. package/x/view/hooks/use-once.d.ts +2 -0
  98. package/x/view/hooks/use-once.js +7 -0
  99. package/x/view/hooks/use-once.js.map +1 -0
  100. package/x/view/hooks/use-op.d.ts +3 -0
  101. package/x/view/hooks/use-op.js +9 -0
  102. package/x/view/hooks/use-op.js.map +1 -0
  103. package/x/view/hooks/use-ref.d.ts +5 -0
  104. package/x/view/hooks/use-ref.js +11 -0
  105. package/x/view/hooks/use-ref.js.map +1 -0
  106. package/x/view/hooks/use-signal.d.ts +3 -0
  107. package/x/view/hooks/use-signal.js +12 -0
  108. package/x/view/hooks/use-signal.js.map +1 -0
  109. package/x/view/hooks/use-state.d.ts +1 -0
  110. package/x/view/hooks/use-state.js +17 -0
  111. package/x/view/hooks/use-state.js.map +1 -0
  112. package/x/view/hooks/use-wake.d.ts +2 -0
  113. package/x/view/hooks/use-wake.js +6 -0
  114. package/x/view/hooks/use-wake.js.map +1 -0
  115. package/x/view/index.d.ts +15 -4
  116. package/x/view/index.js +15 -4
  117. package/x/view/index.js.map +1 -1
  118. package/x/view/light.d.ts +2 -0
  119. package/x/view/light.js +41 -0
  120. package/x/view/light.js.map +1 -0
  121. package/x/view/parts/apply-attrs.d.ts +2 -0
  122. package/x/view/parts/apply-attrs.js +22 -0
  123. package/x/view/parts/apply-attrs.js.map +1 -0
  124. package/x/{base/utils → view/parts}/apply-styles.js.map +1 -1
  125. package/x/view/parts/cx.d.ts +12 -0
  126. package/x/view/parts/cx.js +24 -0
  127. package/x/view/parts/cx.js.map +1 -0
  128. package/x/view/parts/reactivity.d.ts +5 -0
  129. package/x/view/parts/reactivity.js +18 -0
  130. package/x/view/parts/reactivity.js.map +1 -0
  131. package/x/view/parts/sly-shadow.d.ts +3 -0
  132. package/x/view/parts/sly-shadow.js +7 -0
  133. package/x/view/parts/sly-shadow.js.map +1 -0
  134. package/x/view/shadow.d.ts +6 -0
  135. package/x/view/shadow.js +72 -0
  136. package/x/view/shadow.js.map +1 -0
  137. package/x/view/types.d.ts +13 -21
  138. package/s/demo/views/counter.ts +0 -50
  139. package/s/demo/views/fastcount.ts +0 -29
  140. package/x/base/css-reset.js +0 -19
  141. package/x/base/css-reset.js.map +0 -1
  142. package/x/base/element.d.ts +0 -19
  143. package/x/base/element.js +0 -52
  144. package/x/base/element.js.map +0 -1
  145. package/x/base/index.d.ts +0 -5
  146. package/x/base/index.js +0 -6
  147. package/x/base/index.js.map +0 -1
  148. package/x/base/types.d.ts +0 -3
  149. package/x/base/types.js +0 -3
  150. package/x/base/types.js.map +0 -1
  151. package/x/base/use.d.ts +0 -59
  152. package/x/base/use.js +0 -129
  153. package/x/base/use.js.map +0 -1
  154. package/x/base/utils/attr-watcher.d.ts +0 -8
  155. package/x/base/utils/attr-watcher.js +0 -20
  156. package/x/base/utils/attr-watcher.js.map +0 -1
  157. package/x/base/utils/mounts.js.map +0 -1
  158. package/x/base/utils/reactor.d.ts +0 -5
  159. package/x/base/utils/reactor.js +0 -25
  160. package/x/base/utils/reactor.js.map +0 -1
  161. package/x/base/utils/states.d.ts +0 -13
  162. package/x/base/utils/states.js +0 -41
  163. package/x/base/utils/states.js.map +0 -1
  164. package/x/base/utils/use-attrs.d.ts +0 -11
  165. package/x/base/utils/use-attrs.js +0 -18
  166. package/x/base/utils/use-attrs.js.map +0 -1
  167. package/x/demo/views/counter.d.ts +0 -374
  168. package/x/demo/views/counter.js +0 -42
  169. package/x/demo/views/counter.js.map +0 -1
  170. package/x/demo/views/fastcount.d.ts +0 -12
  171. package/x/demo/views/fastcount.js +0 -21
  172. package/x/demo/views/fastcount.js.map +0 -1
  173. package/x/spa/index.barrel.d.ts +0 -4
  174. package/x/spa/index.barrel.js +0 -3
  175. package/x/spa/index.barrel.js.map +0 -1
  176. package/x/spa/index.d.ts +0 -2
  177. package/x/spa/index.js +0 -2
  178. package/x/spa/index.js.map +0 -1
  179. package/x/spa/plumbing/braces.d.ts +0 -12
  180. package/x/spa/plumbing/braces.js +0 -55
  181. package/x/spa/plumbing/braces.js.map +0 -1
  182. package/x/spa/plumbing/primitives.d.ts +0 -22
  183. package/x/spa/plumbing/primitives.js +0 -65
  184. package/x/spa/plumbing/primitives.js.map +0 -1
  185. package/x/spa/plumbing/router-core.d.ts +0 -13
  186. package/x/spa/plumbing/router-core.js +0 -38
  187. package/x/spa/plumbing/router-core.js.map +0 -1
  188. package/x/spa/plumbing/types.d.ts +0 -35
  189. package/x/spa/plumbing/types.js +0 -2
  190. package/x/spa/plumbing/types.js.map +0 -1
  191. package/x/spa/router.d.ts +0 -13
  192. package/x/spa/router.js +0 -39
  193. package/x/spa/router.js.map +0 -1
  194. package/x/spa/spa.test.d.ts +0 -15
  195. package/x/spa/spa.test.js +0 -78
  196. package/x/spa/spa.test.js.map +0 -1
  197. package/x/view/utils/contextualize.d.ts +0 -13
  198. package/x/view/utils/contextualize.js +0 -18
  199. package/x/view/utils/contextualize.js.map +0 -1
  200. package/x/view/utils/make-component.d.ts +0 -5
  201. package/x/view/utils/make-component.js +0 -17
  202. package/x/view/utils/make-component.js.map +0 -1
  203. package/x/view/utils/make-view.d.ts +0 -2
  204. package/x/view/utils/make-view.js +0 -32
  205. package/x/view/utils/make-view.js.map +0 -1
  206. package/x/view/utils/parts/capsule.d.ts +0 -12
  207. package/x/view/utils/parts/capsule.js +0 -50
  208. package/x/view/utils/parts/capsule.js.map +0 -1
  209. package/x/view/utils/parts/chain.d.ts +0 -13
  210. package/x/view/utils/parts/chain.js +0 -26
  211. package/x/view/utils/parts/chain.js.map +0 -1
  212. package/x/view/utils/parts/context.d.ts +0 -9
  213. package/x/view/utils/parts/context.js +0 -10
  214. package/x/view/utils/parts/context.js.map +0 -1
  215. package/x/view/utils/parts/directive.d.ts +0 -5
  216. package/x/view/utils/parts/directive.js +0 -20
  217. package/x/view/utils/parts/directive.js.map +0 -1
  218. package/x/view/utils/parts/naked.d.ts +0 -18
  219. package/x/view/utils/parts/naked.js +0 -57
  220. package/x/view/utils/parts/naked.js.map +0 -1
  221. package/x/view/utils/parts/sly-view.d.ts +0 -6
  222. package/x/view/utils/parts/sly-view.js +0 -16
  223. package/x/view/utils/parts/sly-view.js.map +0 -1
  224. package/x/view/view.d.ts +0 -11
  225. package/x/view/view.js +0 -15
  226. package/x/view/view.js.map +0 -1
  227. /package/s/{base → _archive/base}/css-reset.ts +0 -0
  228. /package/s/{base → _archive/base}/index.ts +0 -0
  229. /package/s/{base → _archive/base}/types.ts +0 -0
  230. /package/s/{base → _archive/base}/use.ts +0 -0
  231. /package/s/{base → _archive/base}/utils/apply-styles.ts +0 -0
  232. /package/s/{base → _archive/base}/utils/attr-watcher.ts +0 -0
  233. /package/s/{base → _archive/base}/utils/mounts.ts +0 -0
  234. /package/s/{base → _archive/base}/utils/reactor.ts +0 -0
  235. /package/s/{base → _archive/base}/utils/states.ts +0 -0
  236. /package/s/{base → _archive/base}/utils/use-attrs.ts +0 -0
  237. /package/s/{spa → _archive/spa}/index.barrel.ts +0 -0
  238. /package/s/{spa → _archive/spa}/index.ts +0 -0
  239. /package/s/{spa → _archive/spa}/plumbing/braces.ts +0 -0
  240. /package/s/{spa → _archive/spa}/plumbing/primitives.ts +0 -0
  241. /package/s/{spa → _archive/spa}/plumbing/router-core.ts +0 -0
  242. /package/s/{spa → _archive/spa}/plumbing/types.ts +0 -0
  243. /package/s/{spa → _archive/spa}/router.ts +0 -0
  244. /package/s/{spa → _archive/spa}/spa.test.ts +0 -0
  245. /package/s/{view → _archive/view}/utils/contextualize.ts +0 -0
  246. /package/s/{view → _archive/view}/utils/make-component.ts +0 -0
  247. /package/s/{view → _archive/view}/utils/make-view.ts +0 -0
  248. /package/s/{view → _archive/view}/utils/parts/chain.ts +0 -0
  249. /package/s/{view → _archive/view}/utils/parts/context.ts +0 -0
  250. /package/s/{view → _archive/view}/utils/parts/directive.ts +0 -0
  251. /package/s/{view → _archive/view}/utils/parts/naked.ts +0 -0
  252. /package/s/{view → _archive/view}/utils/parts/sly-view.ts +0 -0
  253. /package/s/{view → _archive/view}/view.ts +0 -0
  254. /package/x/{base → view/common}/css-reset.d.ts +0 -0
  255. /package/x/{base/utils → view/parts}/apply-styles.d.ts +0 -0
  256. /package/x/{base/utils → view/parts}/apply-styles.js +0 -0
package/README.md CHANGED
@@ -4,234 +4,167 @@
4
4
  # 🦝 sly
5
5
  > *mischievous shadow views*
6
6
 
7
+ ```sh
8
+ npm install lit @e280/sly @e280/strata @e280/stz
9
+ ```
10
+
7
11
  [@e280](https://e280.org/)'s new [lit](https://lit.dev/)-based frontend webdev library.
8
12
 
9
- - 🍋 [**#views**](#views) — shadow-dom'd, hooks-based, componentizable
10
- - 🪵 [**#base-element**](#base-element) — for a more classical experience
11
- - 🪄 [**#dom**](#dom) — the "it's not jquery" multitool
13
+ - 🍋 [**#views**](#views) — hooks-based, shadow-dom'd, template-literal'd
14
+ - 🪝 [**#hooks**](#hooks) — full reference of available view hooks
12
15
  - 🫛 [**#ops**](#ops) — reactive tooling for async operations
13
16
  - ⏳ [**#loaders**](#loaders) — animated loading spinners for rendering ops
14
- - 💅 [**#spa**](#spa) — hash routing for your spa-day
15
17
  - 🪙 [**#loot**](#loot) — drag-and-drop facilities
18
+ - 🪄 [**#dom**](#dom) — the "it's not jquery" multitool
16
19
  - 🧪 https://sly.e280.org/ — our testing page
17
- - **✨[shiny](https://shiny.e280.org/)✨** — our wip component library
18
20
 
19
21
 
20
22
 
21
23
  <br/><br/>
24
+ <a id="views"></a>
22
25
 
23
- ## 🦝 sly and friends
24
- > `@e280/sly`
26
+ ## 🍋 views
27
+ > *modern views.. in lightness, or darkness..*
25
28
 
26
- ```sh
27
- npm install @e280/sly lit @e280/strata @e280/stz
28
- ```
29
+ - 🪶 **no compile step** — just god's honest javascript, via [lit](https://lit.dev/)-html tagged-template-literals
30
+ - **reactive** views auto-rerender whenever any [strata](https://github.com/e280/strata)-compatible state changes
31
+ - 🪝 **hooks-based** — declarative rendering with [modern hooks](#hooks) familiar to react devs
29
32
 
30
- > [!NOTE]
31
- > - 🔥 [lit](https://lit.dev/), for html rendering
32
- > - ⛏️ [@e280/strata](https://github.com/e280/strata), for state management (signals, state trees)
33
- > - 🏂 [@e280/stz](https://github.com/e280/stz), our ts standard library
34
- > - 🐢 [@e280/scute](https://github.com/e280/scute), our buildy-bundly-buddy
33
+ ```ts
34
+ import {html} from "lit"
35
+ import {light, shadow, dom} from "@e280/sly"
35
36
 
36
- > [!TIP]
37
- > you can import everything in sly from `@e280/sly`,
38
- > or from specific subpackages like `@e280/sly/view`, `@e280/sly/dom`, etc...
37
+ export const MyLightView = light(() => html`<p>blinded by the light</p>`)
39
38
 
39
+ export const MyShadowView = shadow(() => html`<p>shrouded in darkness</p>`)
40
+ ```
40
41
 
42
+ ### 🌞 light views
43
+ > *just pretend it's react*
41
44
 
42
- <br/><br/>
43
- <a id="views"></a>
45
+ - **define a light view**
46
+ ```ts
47
+ import {html} from "lit"
48
+ import {light, useSignal} from "@e280/sly"
44
49
 
45
- ## 🍋🦝 sly views
46
- > `@e280/sly/view`
47
- > *the crown jewel of sly*
50
+ export const MyCounter = light((start: number) => {
51
+ const $count = useSignal(start)
52
+ const increment = () => $count.value++
48
53
 
49
- ```ts
50
- view(use => () => html`<p>hello world</p>`)
51
- ```
54
+ return html`
55
+ <button @click="${increment}">${$count.value}</button>
56
+ `
57
+ })
58
+ ```
59
+ - **render it into the dom**
60
+ ```ts
61
+ dom.in(".demo").render(html`
62
+ <h1>my cool counter demo</h1>
63
+ ${MyCounter(123)}
64
+ `)
65
+ ```
52
66
 
53
- - 🪶 **no compile step** — just god's honest javascript, via [lit](https://lit.dev/)-html tagged-template-literals
54
- - 🥷 **shadow dom'd** — each view gets its own cozy [shadow](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) bubble, and supports [slots](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots)
55
- - 🪝 **hooks-based** — declarative rendering with the [`use`](#use) family of ergonomic hooks
56
- - ⚡ **reactive** — they auto-rerender whenever any [strata](https://github.com/e280/strata)-compatible state changes
57
- - 🧐 **not components, per se** — they're comfy typescript-native ui building blocks [(technically, lit directives)](https://lit.dev/docs/templates/custom-directives/)
58
- - 🧩 **componentizable** — any view can be magically converted into a proper [web component](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)
67
+ ### 🌚 shadow views
68
+ > *each shadow view gets its own cozy [shadow-dom](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) bubble and supports [slots](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots)*
59
69
 
60
- ### 🍋 view example
61
- ```ts
62
- import {view, dom, BaseElement} from "@e280/sly"
63
- import {html, css} from "lit"
64
- ```
65
- - **declare view**
70
+ - **define a shadow view**
66
71
  ```ts
67
- export const CounterView = view(use => (start: number) => {
68
- use.styles(css`p {color: green}`)
72
+ import {css, html} from "lit"
73
+ import {shadow, useName, useCss, useSignal} from "@e280/sly"
74
+
75
+ export const MyShadowCounter = shadow((start: number) => {
76
+ useName("shadow-counter")
77
+ useCss(css`button { color: cyan }`)
69
78
 
70
- const $count = use.signal(start)
79
+ const $count = useSignal(start)
71
80
  const increment = () => $count.value++
72
81
 
73
82
  return html`
74
- <button @click="${increment}">
75
- ${$count.value}
76
- </button>
83
+ <button @click="${increment}">${$count()}</button>
84
+ <slot></slot>
77
85
  `
78
86
  })
79
87
  ```
80
- - `$count` is a [strata signal](https://github.com/e280/strata#readme) *(we like those)*
81
- - **inject view into dom**
88
+ - **render it into the dom**
82
89
  ```ts
83
- dom.in(".app").render(html`
84
- <h1>cool counter demo</h1>
85
- ${CounterView(1)}
90
+ dom.in(".demo").render(html`
91
+ <h1>my cool counter demo</h1>
92
+ ${MyShadowCounter(234)}
86
93
  `)
87
94
  ```
88
- - 🤯 **register view as web component**
95
+ - **.with to nest children or set attrs**
89
96
  ```ts
90
- dom.register({
91
- MyCounter: CounterView
92
- .component()
93
- .props(() => [1]),
94
- })
97
+ dom.in(".demo").render(html`
98
+ <h1>my cool counter demo</h1>
99
+
100
+ ${MyShadowCounter.with({
101
+ props: [234],
102
+ attrs: {"data-whatever": 555},
103
+ children: html`
104
+ <p>woah, slotting support!</p>
105
+ `,
106
+ })}
107
+ `)
95
108
  ```
96
- ```html
97
- <my-counter></my-counter>
109
+ - **oh, you can do custom shadow config if needed**
110
+ ```ts
111
+ const MyCustomShadow = shadow.config(() => {
112
+ const host = document.createElement("div")
113
+ const shadow = host.attachShadow({mode: "open"})
114
+ return {host, shadow}
115
+ })(() => html`<p>shrouded in darkness</p>`)
98
116
  ```
99
117
 
100
- ### 🍋 view settings
101
- - optional settings for views you should know about
118
+
119
+
120
+ <br/><br/>
121
+ <a id="hooks"></a>
122
+
123
+ ## 🪝 hooks
124
+ > *composable view state and utilities*
125
+
126
+ ### 👮 follow the hooks rules
127
+
128
+ just like [react hooks](https://react.dev/warnings/invalid-hook-call-warning), the execution order of sly's `use` hooks actually matters.
129
+
130
+ you must not call these hooks under `if` conditionals, or `for` loops, or in callbacks, or after a conditional `return` statement, or anything like that.. *otherwise, heed my warning: weird bad stuff will happen..*
131
+
132
+ ### 🌚 shadow-only hooks
133
+ - **useName** — *(shadow only)* — set the "view" attribute value
102
134
  ```ts
103
- export const CoolView = view
104
- .settings({mode: "open", delegatesFocus: true})
105
- .render(use => (greeting: string) => html`😎 ${greeting} <slot></slot>`)
135
+ useName("squarepants")
136
+ // <sly-shadow view="squarepants">
106
137
  ```
107
- - all [attachShadow params](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#parameters) (like `mode` and `delegatesFocus`) are valid `settings`
108
- - note the `<slot></slot>` we'll use in the next example lol
109
-
110
- ### 🍋 view chains
111
- - views have this sick chaining syntax for supplying more stuff at the template injection site
138
+ - **useCss** *(shadow only)* — attach stylesheets (use lit's `css`!) to the shadow root
112
139
  ```ts
113
- dom.in(".app").render(html`
114
- <h2>cool example</h2>
115
- ${CoolView
116
- .props("hello")
117
- .attr("class", "hero")
118
- .children(html`<em>spongebob</em>`)
119
- .render()}
120
- `)
140
+ useCss(css1, css2, css3)
121
141
  ```
122
- - `props`provide props and start a view chain
123
- - `attr` — set html attributes on the `<sly-view>` host element
124
- - `children` — add nested [slottable](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots) content
125
- - `render` — end the view chain and render the lit directive
126
-
127
- ### 🍋 view/component universality
128
- - **you can start with a view,**
142
+ - **useHost***(shadow only)* — get the host element
129
143
  ```ts
130
- export const GreeterView = view(use => (name: string) => {
131
- return html`<p>hello ${name}</p>`
132
- })
144
+ const host = useHost()
133
145
  ```
134
- - view usage
135
- ```ts
136
- GreeterView("pimsley")
137
- ```
138
- **then you can convert it to a component.**
139
- ```ts
140
- export class GreeterComponent extends (
141
- GreeterView
142
- .component()
143
- .props(component => [component.getAttribute("name") ?? "unknown"])
144
- ) {}
145
- ```
146
- - html usage
147
- ```html
148
- <greeter-component name="pimsley"></greeter-component>
149
- ```
150
- - **you can start with a component,**
151
- ```ts
152
- export class GreeterComponent extends (
153
- view(use => (name: string) => {
154
- return html`<p>hello ${name}</p>`
155
- })
156
- .component()
157
- .props(component => [component.getAttribute("name") ?? "unknown"])
158
- ) {}
159
- ```
160
- - html usage
161
- ```html
162
- <greeter-component name="pimsley"></greeter-component>
163
- ```
164
- **and it already has `.view` ready for you.**
165
- - view usage
166
- ```ts
167
- GreeterComponent.view("pimsley")
168
- ```
169
- - **understanding `.component(BaseElement)` and `.props(fn)`**
170
- - `.props` takes a fn that is called every render, which returns the props given to the view
171
- ```ts
172
- .props(() => ["pimsley"])
173
- ```
174
- the props fn receives the component instance, so you can query html attributes or instance properties
175
- ```ts
176
- .props(component => [component.getAttribute("name") ?? "unknown"])
177
- ```
178
- - `.component` accepts a subclass of `BaseElement`, so you can define your own properties and methods for your component class
179
- ```ts
180
- const GreeterComponent = GreeterView
181
-
182
- // declare your own custom class
183
- .component(class extends BaseElement {
184
- $name = signal("jim raynor")
185
- updateName(name: string) {
186
- this.$name.value = name
187
- }
188
- })
189
-
190
- // props gets the right types on 'component'
191
- .props(component => [component.$name.value])
192
- ```
193
- - `.component` provides the devs interacting with your component, with noice typings
194
- ```ts
195
- dom<GreeterComponent>("greeter-component").updateName("mortimer")
196
- ```
197
- - typescript class wizardry
198
- - ❌ smol-brain approach exports class value, but NOT the typings
199
- ```ts
200
- export const GreeterComponent = (...)
201
- ```
202
- - ✅ giga-brain approach exports class value AND the typings
203
- ```ts
204
- export class GreeterComponent extends (...) {}
205
- ```
206
- - **register web components to the dom**
207
- ```ts
208
- dom.register({GreeterComponent})
209
- ```
210
- - **oh and don't miss out on the insta-component shorthand**
211
- ```ts
212
- dom.register({
213
- QuickComponent: view.component(use => html`⚡ incredi`),
214
- })
146
+ - **useShadow** — *(shadow only)* — get the shadow root
147
+ ```ts
148
+ const shadow = useShadow()
215
149
  ```
216
150
 
217
- <a id="use"></a>
218
-
219
- ### 🍋 "use" hooks reference
220
- - 👮 **follow the hooks rules**
221
- > just like [react hooks](https://react.dev/warnings/invalid-hook-call-warning), the execution order of sly's `use` hooks actually matters..
222
- > you must not call these hooks under `if` conditionals, or `for` loops, or in callbacks, or after a conditional `return` statement, or anything like that.. *otherwise, heed my warning: weird bad stuff will happen..*
223
- - **use.name** — set the "view" attr value, eg `<sly-view view="squarepants">`
151
+ ### 🌞 universal hooks
152
+ - **useState** — react-like hook to create some reactive state (we prefer signals)
224
153
  ```ts
225
- use.name("squarepants")
154
+ const [count, setCount] = useState(0)
155
+
156
+ const increment = () => setCount(n => n + 1)
226
157
  ```
227
- - **use.styles** — attach stylesheets into the view's shadow dom
158
+ - **useRef** — react-like hook to make a non-reactive box for a value
228
159
  ```ts
229
- use.styles(css1, css2, css3)
160
+ const ref = useRef(0)
161
+
162
+ ref.current // 0
163
+ ref.current = 1 // does not trigger rerender
230
164
  ```
231
- *(alias `use.css`)*
232
- - **use.signal** — create a [strata signal](https://github.com/e280/strata)
165
+ - **useSignal** — create a [strata](https://github.com/e280/strata) signal
233
166
  ```ts
234
- const $count = use.signal(1)
167
+ const $count = useSignal(1)
235
168
 
236
169
  // read the signal
237
170
  $count()
@@ -239,391 +172,95 @@ import {html, css} from "lit"
239
172
  // write the signal
240
173
  $count(2)
241
174
  ```
242
- - `derived` signals
243
- ```ts
244
- const $product = use.derived(() => $count() * $whatever())
245
- ```
246
- - `lazy` signals
247
- ```ts
248
- const $product = use.lazy(() => $count() * $whatever())
249
- ```
250
- - go read the [strata readme](https://github.com/e280/strata) about this stuff
251
- - **use.once** run fn at initialization, and return a value
252
- ```ts
253
- const whatever = use.once(() => {
254
- console.log("happens only once")
175
+ - see [strata readme](https://github.com/e280/strata)
176
+ - **useDerived** — create a [strata](https://github.com/e280/strata) derived signal
177
+ ```ts
178
+ const $product = useDerived(() => $count() * $whatever())
179
+ ```
180
+ - see [strata readme](https://github.com/e280/strata)
181
+ - **useOnce** run fn at initialization, and return a value
182
+ ```ts
183
+ const whatever = useOnce(() => {
184
+ console.log("happens one time")
255
185
  return 123
256
186
  })
257
187
 
258
188
  whatever // 123
259
189
  ```
260
- - **use.mount** — setup mount/unmount lifecycle
190
+ - **useMount** — setup mount/unmount lifecycle
261
191
  ```ts
262
- use.mount(() => {
263
- console.log("view mounted")
264
-
265
- return () => {
266
- console.log("view unmounted")
267
- }
192
+ useMount(() => {
193
+ console.log("mounted")
194
+ return () => console.log("unmounted")
268
195
  })
269
196
  ```
270
- - **use.wake** — run fn each time mounted, and return value
197
+ - **useWake** — run fn each time mounted, and return value
271
198
  ```ts
272
- const whatever = use.wake(() => {
273
- console.log("view mounted")
199
+ const whatever = useWake(() => {
200
+ console.log("mounted")
274
201
  return 123
275
202
  })
276
203
 
277
204
  whatever // 123
278
205
  ```
279
- - **use.life** — mount/unmount lifecycle, but also return a value
206
+ - **useLife** — mount/unmount lifecycle, but also return a value
280
207
  ```ts
281
- const v = use.life(() => {
208
+ const whatever = useLife(() => {
282
209
  console.log("mounted")
283
210
  const value = 123
284
211
  return [value, () => console.log("unmounted")]
285
212
  })
286
213
 
287
- v // 123
214
+ whatever // 123
288
215
  ```
289
- - **use.events** — attach event listeners to the element (auto-cleaned up)
216
+ - **useRender** — returns a fn to rerender the view (debounced)
290
217
  ```ts
291
- use.events({
292
- keydown: (e: KeyboardEvent) => console.log("keydown", e.code),
293
- keyup: (e: KeyboardEvent) => console.log("keyup", e.code),
294
- })
218
+ const render = useRender()
219
+
220
+ render().then(() => console.log("render done"))
295
221
  ```
296
- - **use.states** — [internal states](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states) helper
222
+ - **useRendered** — get a promise that resolves *after* the next render
297
223
  ```ts
298
- const states = use.states()
299
- states.assign("active", "cool")
300
- ```
301
- ```css
302
- [view="my-view"]::state(active) { color: yellow; }
303
- [view="my-view"]::state(cool) { outline: 1px solid cyan; }
304
- ```
305
- - **use.attrs** — ergonomic typed html attribute access
306
- - `use.attrs` is similar to [#dom.attrs](#dom.attrs)
307
- ```ts
308
- const attrs = use.attrs({
309
- name: String,
310
- count: Number,
311
- active: Boolean,
312
- })
313
- ```
314
- ```ts
315
- attrs.name // "chase"
316
- attrs.count // 123
317
- attrs.active // true
318
- ```
319
- - use.attrs.{strings/numbers/booleans}
320
- ```ts
321
- use.attrs.strings.name // "chase"
322
- use.attrs.numbers.count // 123
323
- use.attrs.booleans.active // true
324
- ```
325
- - use.attrs.on
326
- ```ts
327
- use.attrs.on(() => console.log("an attribute changed"))
328
- ```
329
- - **use.render** — rerender the view (debounced)
330
- ```ts
331
- use.render()
332
- ```
333
- - **use.renderNow** — rerender the view instantly (not debounced)
334
- ```ts
335
- use.renderNow()
336
- ```
337
- - **use.rendered** — promise that resolves *after* the next render
338
- ```ts
339
- use.rendered.then(() => {
340
- const slot = use.shadow.querySelector("slot")
341
- console.log(slot)
342
- })
224
+ useRendered().then(() => console.log("rendered"))
343
225
  ```
344
- - **use.op** — start with an op based on an async fn
226
+ - **useOp** — start loading an op based on an async fn
345
227
  ```ts
346
- const op = use.op(async() => {
228
+ const op = useOp(async() => {
347
229
  await nap(5000)
348
230
  return 123
349
231
  })
350
232
  ```
351
- - **use.op.promise** — start with an op based on a promise
233
+ - **useOpPromise** — start loading an op based on a promise
352
234
  ```ts
353
- const op = use.op.promise(doAsyncWork())
235
+ const op = useOpPromise(doAsyncWork())
354
236
  ```
355
237
 
356
- ### 🍋 "use" recipes
238
+ ### 🧑‍🍳 happy hooks recipes
357
239
  - make a ticker — mount, cycle, and nap
358
240
  ```ts
359
241
  import {cycle, nap} from "@e280/stz"
360
242
  ```
361
243
  ```ts
362
- const $seconds = use.signal(0)
244
+ const $seconds = useSignal(0)
363
245
 
364
- use.mount(() => cycle(async() => {
246
+ useMount(() => cycle(async() => {
365
247
  await nap(1000)
366
248
  $seconds.value++
367
249
  }))
368
250
  ```
369
251
  - wake + rendered, to do something after each mount's first render
370
252
  ```ts
371
- use.wake(() => use.rendered.then(() => {
253
+ useWake(() => useRendered.then(() => {
372
254
  console.log("after first render")
373
255
  }))
374
256
  ```
375
257
 
376
258
 
377
259
 
378
- <br/><br/>
379
- <a id="base-element"></a>
380
-
381
- ## 🪵🦝 sly base element
382
- > `@e280/sly/base`
383
- > *the classic experience*
384
-
385
- ```ts
386
- import {BaseElement, Use, dom} from "@e280/sly"
387
- import {html, css} from "lit"
388
- ```
389
-
390
- `BaseElement` is more of an old-timey class-based "boomer" approach to making web components, but with a millennial twist — its `render` method gives you the same `use` hooks that views enjoy.
391
-
392
- 👮 a *BaseElement* is not a *View*, and cannot be converted into a *View*.
393
-
394
- ### 🪵 let's clarify some sly terminology
395
- - "Element"
396
- - an html element; any subclass of the browser's HTMLElement
397
- - all genuine ["web components"](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) are elements
398
- - "BaseElement"
399
- - sly's own subclass of the browser-native HTMLElement
400
- - is a true element and web component (can be registered to the dom)
401
- - "View"
402
- - sly's own magic concept that uses a lit-directive to render stuff
403
- - NOT an element or web component (can NOT be registered to the dom)
404
- - NOT related to BaseElement
405
- - can be converted into a Component via `view.component().props(() => [])`
406
- - "Component"
407
- - a sly view that has been converted into an element
408
- - is a true element and web component (can be registered to the dom)
409
- - actually a subclass of BaseElement
410
- - actually contains the view on `Component.view`
411
-
412
- ### 🪵 base element setup
413
- - **declare your element class**
414
- ```ts
415
- export class MyElement extends BaseElement {
416
- static styles = css`span{color:orange}`
417
-
418
- // custom property
419
- $start = signal(10)
420
-
421
- // custom attributes
422
- attrs = dom.attrs(this).spec({
423
- multiply: Number,
424
- })
425
-
426
- // custom methods
427
- hello() {
428
- return "world"
429
- }
430
-
431
- render(use: Use) {
432
- const $count = use.signal(1)
433
- const increment = () => $count.value++
434
-
435
- const {$start} = this
436
- const {multiply = 1} = this.attrs
437
- const result = $start() + (multiply * $count())
438
-
439
- return html`
440
- <span>${result}</span>
441
- <button @click="${increment}">+</button>
442
- `
443
- }
444
- }
445
- ```
446
- - **register your element to the dom**
447
- ```ts
448
- dom.register({MyElement})
449
- ```
450
-
451
- ### 🪵 base element usage
452
- - **place the element in your html body**
453
- ```html
454
- <body>
455
- <my-element></my-element>
456
- </body>
457
- ```
458
- - **now you can interact with it**
459
- ```ts
460
- const myElement = dom<MyElement>("my-element")
461
-
462
- // js property
463
- myElement.$start(100)
464
-
465
- // html attributes
466
- myElement.attrs.multiply = 2
467
-
468
- // methods
469
- myElement.hello()
470
- // "world"
471
- ```
472
-
473
-
474
-
475
- <br/><br/>
476
- <a id="dom"></a>
477
-
478
- ## 🪄🦝 sly dom
479
- > `@e280/sly/dom`
480
- > *the "it's not jquery!" multitool*
481
-
482
- ```ts
483
- import {dom} from "@e280/sly"
484
- ```
485
-
486
- ### 🪄 dom queries
487
- - `require` an element
488
- ```ts
489
- dom(".demo")
490
- // HTMLElement (or throws)
491
- ```
492
- ```ts
493
- // alias
494
- dom.require(".demo")
495
- // HTMLElement (or throws)
496
- ```
497
- - `maybe` get an element
498
- ```ts
499
- dom.maybe(".demo")
500
- // HTMLElement | undefined
501
- ```
502
- - `all` matching elements in an array
503
- ```ts
504
- dom.all(".demo ul li")
505
- // HTMLElement[]
506
- ```
507
-
508
- ### 🪄 dom.in scope
509
- - make a scope
510
- ```ts
511
- dom.in(".demo") // selector
512
- // Dom instance
513
- ```
514
- ```ts
515
- dom.in(demoElement) // element
516
- // Dom instance
517
- ```
518
- - run queries in that scope
519
- ```ts
520
- dom.in(demoElement).require(".button")
521
- ```
522
- ```ts
523
- dom.in(demoElement).maybe(".button")
524
- ```
525
- ```ts
526
- dom.in(demoElement).all("ol li")
527
- ```
528
-
529
- ### 🪄 dom utilities
530
- - `dom.register` web components
531
- ```ts
532
- dom.register({MyComponent, AnotherCoolComponent})
533
- // <my-component>
534
- // <another-cool-component>
535
- ```
536
- - `dom.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
537
- - `dom.render` content into an element
538
- ```ts
539
- dom.render(element, html`<p>hello world</p>`)
540
- ```
541
- ```ts
542
- dom.in(".demo").render(html`<p>hello world</p>`)
543
- ```
544
- - `dom.el` little element builder
545
- ```ts
546
- const div = dom.el("div", {"data-whatever": 123, "data-active": true})
547
- // <div data-whatever="123" data-active></div>
548
- ```
549
- - `dom.elmer` make an element with a fluent chain
550
- ```ts
551
- const div = dom.elmer("div")
552
- .attr("data-whatever", 123)
553
- .attr("data-active")
554
- .children("hello world")
555
- .done()
556
- // HTMLElement
557
- ```
558
- - `dom.mk` make an element with a lit template (returns the first)
559
- ```ts
560
- const div = dom.mk(html`
561
- <div data-whatever="123" data-active>
562
- hello world
563
- </div>
564
- `) // HTMLElement
565
- ```
566
- - `dom.events` <a id="dom.events"></a> to attach event listeners
567
- ```ts
568
- const detach = dom.events(element, {
569
- keydown: (e: KeyboardEvent) => console.log("keydown", e.code),
570
- keyup: (e: KeyboardEvent) => console.log("keyup", e.code),
571
- })
572
- ```
573
- ```ts
574
- const detach = dom.in(".demo").events({
575
- keydown: (e: KeyboardEvent) => console.log("keydown", e.code),
576
- keyup: (e: KeyboardEvent) => console.log("keyup", e.code),
577
- })
578
- ```
579
- ```ts
580
- // unattach those event listeners when you're done
581
- detach()
582
- ```
583
- - `dom.attrs` <a id="dom.attrs"></a> to setup a type-happy html attribute helper
584
- ```ts
585
- const attrs = dom.attrs(element).spec({
586
- name: String,
587
- count: Number,
588
- active: Boolean,
589
- })
590
- ```
591
- ```ts
592
- const attrs = dom.in(".demo").attrs.spec({
593
- name: String,
594
- count: Number,
595
- active: Boolean,
596
- })
597
- ```
598
- ```ts
599
- attrs.name // "chase"
600
- attrs.count // 123
601
- attrs.active // true
602
- ```
603
- ```ts
604
- attrs.name = "zenky"
605
- attrs.count = 124
606
- attrs.active = false // removes html attr
607
- ```
608
- ```ts
609
- attrs.name = undefined // removes the attr
610
- attrs.count = undefined // removes the attr
611
- ```
612
- or if you wanna be more loosey-goosey, skip the spec
613
- ```ts
614
- const a = dom.in(".demo").attrs
615
- a.strings.name = "pimsley"
616
- a.numbers.count = 125
617
- a.booleans.active = true
618
- ```
619
-
620
-
621
-
622
260
  <br/><br/>
623
261
  <a id="ops"></a>
624
262
 
625
- ## 🫛🦝 sly ops
626
- > `@e280/sly/ops`
263
+ ## 🫛 ops
627
264
  > *tools for async operations and loading spinners*
628
265
 
629
266
  ```ts
@@ -732,8 +369,7 @@ import {Pod, podium, Op, loaders} from "@e280/sly"
732
369
  <br/><br/>
733
370
  <a id="loaders"></a>
734
371
 
735
- ## ⏳🦝 sly loaders
736
- > `@e280/sly/loaders`
372
+ ## loaders
737
373
  > *animated loading spinners for ops*
738
374
 
739
375
  ```ts
@@ -765,100 +401,10 @@ import {loaders} from "@e280/sly"
765
401
 
766
402
 
767
403
 
768
- <br/><br/>
769
- <a id="spa"></a>
770
-
771
- ## 💅🦝 sly spa
772
- > `@e280/sly/spa`
773
- > *hash router for single-page-apps*
774
-
775
- ```ts
776
- import {spa, html} from "@e280/sly"
777
- ```
778
-
779
- ### 💅 spa.Router basics
780
- - **make a spa router**
781
- ```ts
782
- const router = new spa.Router({
783
- routes: {
784
- home: spa.route("#/", async() => html`home`),
785
- settings: spa.route("#/settings", async() => html`settings`),
786
- user: spa.route("#/user/{userId}", async({userId}) => html`user ${userId}`),
787
- },
788
- })
789
- ```
790
- - all route strings must start with `#/`
791
- - use braces like `{userId}` to accept string params
792
- - home-equivalent hashes like `""` and `"#"` are normalized to `"#/"`
793
- - the router has an effect on the appearance of the url in the browser address bar -- the home `#/` is removed, aesthetically, eg, `e280.org/#/` is rewritten to `e280.org` using *history.replaceState*
794
- - you can provide `loader` option if you want to specify the loading spinner (defaults to `loaders.make()`)
795
- - you can provide `notFound` option, if you want to specify what is shown on invalid routes (defaults to `() => null`)
796
- - when `auto` is true (default), the router calls `.refresh()` and `.listen()` in the constructor.. set it to `false` if you want manual control
797
- - you can set `auto` option false if you want to omit the default initial refresh and listen calls
798
- - **render your current page**
799
- ```ts
800
- return html`
801
- <div class="my-page">
802
- ${router.render()}
803
- </div>
804
- `
805
- ```
806
- - returns lit content
807
- - shows a loading spinner when pages are loading
808
- - will display the notFound content for invalid routes (defaults to null)
809
- - **perform navigations**
810
- - go to settings page
811
- ```ts
812
- await router.nav.settings.go()
813
- // goes to "#/settings"
814
- ```
815
- - go to user page
816
- ```ts
817
- await router.nav.user.go("123")
818
- // goes to "#/user/123"
819
- ```
820
-
821
- ### 💅 spa.Router advanced
822
- - **generate a route's hash string**
823
- ```ts
824
- const hash = router.nav.user.hash("123")
825
- // "#/user/123"
826
-
827
- html`<a href="${hash}">user 123</a>`
828
- ```
829
- - **check if a route is the currently-active one**
830
- ```ts
831
- const hash = router.nav.user.active
832
- // true
833
- ```
834
- - **force-refresh the router**
835
- ```ts
836
- await router.refresh()
837
- ```
838
- - **force-navigate the router by hash**
839
- ```ts
840
- await router.refresh("#/user/123")
841
- ```
842
- - **get the current hash string (normalized)**
843
- ```ts
844
- router.hash
845
- // "#/user/123"
846
- ```
847
- - **the `route(...)` helper fn enables the braces-params syntax**
848
- - but, if you wanna do it differently, you *can* implement your own hash parser to do your own funky syntax
849
- - **dispose the router when you're done with it**
850
- ```ts
851
- router.dispose()
852
- // stop listening to hashchange events
853
- ```
854
-
855
-
856
-
857
404
  <br/><br/>
858
405
  <a id="loot"></a>
859
406
 
860
- ## 🪙🦝 loot
861
- > `@e280/sly/loot`
407
+ ## 🪙 loot
862
408
  > *drag-and-drop facilities*
863
409
 
864
410
  ```ts
@@ -881,7 +427,7 @@ import {ev} from "@e280/stz"
881
427
  - **attach event listeners to your dropzone,** one of these ways:
882
428
  - **view example**
883
429
  ```ts
884
- view(() => () => html`
430
+ light(() => html`
885
431
  <div
886
432
  ?data-indicator="${drops.$indicator()}"
887
433
  @dragover="${drops.dragover}"
@@ -937,9 +483,9 @@ import {ev} from "@e280/stz"
937
483
  ```
938
484
  - **attach dragzone listeners** (there can be many dragzones...)
939
485
  ```ts
940
- view(use => () => {
941
- const money = use.once((): Money => ({value: 280}))
942
- const dragzone = use.once(() => dnd.dragzone(() => money))
486
+ light(() => {
487
+ const money = useOnce((): Money => ({value: 280}))
488
+ const dragzone = useOnce(() => dnd.dragzone(() => money))
943
489
 
944
490
  return html`
945
491
  <div
@@ -953,9 +499,9 @@ import {ev} from "@e280/stz"
953
499
  ```
954
500
  - **attach dropzone listeners** (there can be many dropzones...)
955
501
  ```ts
956
- view(use => () => {
957
- const bag = use.once((): Bag => ({id: 1}))
958
- const dropzone = use.once(() => dnd.dropzone(() => bag))
502
+ light(() => {
503
+ const bag = useOnce((): Bag => ({id: 1}))
504
+ const dropzone = useOnce(() => dnd.dropzone(() => bag))
959
505
  const indicator = !!(dnd.dragging && dnd.hovering === bag)
960
506
 
961
507
  return html`
@@ -978,13 +524,155 @@ import {ev} from "@e280/stz"
978
524
 
979
525
 
980
526
  <br/><br/>
981
- <a id="e280"></a>
527
+ <a id="dom"></a>
982
528
 
983
- ## 🧑‍💻🦝 sly is by e280
984
- reward us with github stars
985
- build with us at https://e280.org/ but only if you're cool
529
+ ## 🪄 dom
530
+ > *the "it's not jquery!" multitool*
531
+
532
+ ```ts
533
+ import {dom} from "@e280/sly"
534
+ ```
535
+
536
+ ### 🪄 dom queries
537
+ - `require` an element
538
+ ```ts
539
+ dom(".demo")
540
+ // HTMLElement (or throws)
541
+ ```
542
+ ```ts
543
+ // alias
544
+ dom.require(".demo")
545
+ // HTMLElement (or throws)
546
+ ```
547
+ - `maybe` get an element
548
+ ```ts
549
+ dom.maybe(".demo")
550
+ // HTMLElement | undefined
551
+ ```
552
+ - `all` matching elements in an array
553
+ ```ts
554
+ dom.all(".demo ul li")
555
+ // HTMLElement[]
556
+ ```
557
+
558
+ ### 🪄 dom.in scope
559
+ - make a scope
560
+ ```ts
561
+ dom.in(".demo") // selector
562
+ // Dom instance
563
+ ```
564
+ ```ts
565
+ dom.in(demoElement) // element
566
+ // Dom instance
567
+ ```
568
+ - run queries in that scope
569
+ ```ts
570
+ dom.in(demoElement).require(".button")
571
+ ```
572
+ ```ts
573
+ dom.in(demoElement).maybe(".button")
574
+ ```
575
+ ```ts
576
+ dom.in(demoElement).all("ol li")
577
+ ```
578
+
579
+ ### 🪄 dom utilities
580
+ - `dom.register` web components
581
+ ```ts
582
+ dom.register({MyComponent, AnotherCoolComponent})
583
+ // <my-component>
584
+ // <another-cool-component>
585
+ ```
586
+ - `dom.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
587
+ - `dom.render` content into an element
588
+ ```ts
589
+ dom.render(element, html`<p>hello world</p>`)
590
+ ```
591
+ ```ts
592
+ dom.in(".demo").render(html`<p>hello world</p>`)
593
+ ```
594
+ - `dom.el` little element builder
595
+ ```ts
596
+ const div = dom.el("div", {"data-whatever": 123, "data-active": true})
597
+ // <div data-whatever="123" data-active></div>
598
+ ```
599
+ - `dom.elmer` make an element with a fluent chain
600
+ ```ts
601
+ const div = dom.elmer("div")
602
+ .attr("data-whatever", 123)
603
+ .attr("data-active")
604
+ .children("hello world")
605
+ .done()
606
+ // HTMLElement
607
+ ```
608
+ - `dom.mk` make an element with a lit template (returns the first)
609
+ ```ts
610
+ const div = dom.mk(html`
611
+ <div data-whatever="123" data-active>
612
+ hello world
613
+ </div>
614
+ `) // HTMLElement
615
+ ```
616
+ - `dom.events` <a id="dom.events"></a> to attach event listeners
617
+ ```ts
618
+ const detach = dom.events(element, {
619
+ keydown: (e: KeyboardEvent) => console.log("keydown", e.code),
620
+ keyup: (e: KeyboardEvent) => console.log("keyup", e.code),
621
+ })
622
+ ```
623
+ ```ts
624
+ const detach = dom.in(".demo").events({
625
+ keydown: (e: KeyboardEvent) => console.log("keydown", e.code),
626
+ keyup: (e: KeyboardEvent) => console.log("keyup", e.code),
627
+ })
628
+ ```
629
+ ```ts
630
+ // unattach those event listeners when you're done
631
+ detach()
632
+ ```
633
+ - `dom.attrs` <a id="dom.attrs"></a> to setup a type-happy html attribute helper
634
+ ```ts
635
+ const attrs = dom.attrs(element).spec({
636
+ name: String,
637
+ count: Number,
638
+ active: Boolean,
639
+ })
640
+ ```
641
+ ```ts
642
+ const attrs = dom.in(".demo").attrs.spec({
643
+ name: String,
644
+ count: Number,
645
+ active: Boolean,
646
+ })
647
+ ```
648
+ ```ts
649
+ attrs.name // "chase"
650
+ attrs.count // 123
651
+ attrs.active // true
652
+ ```
653
+ ```ts
654
+ attrs.name = "zenky"
655
+ attrs.count = 124
656
+ attrs.active = false // removes html attr
657
+ ```
658
+ ```ts
659
+ attrs.name = undefined // removes the attr
660
+ attrs.count = undefined // removes the attr
661
+ ```
662
+ or if you wanna be more loosey-goosey, skip the spec
663
+ ```ts
664
+ const a = dom.in(".demo").attrs
665
+ a.strings.name = "pimsley"
666
+ a.numbers.count = 125
667
+ a.booleans.active = true
668
+ ```
986
669
 
987
670
 
988
671
 
989
672
  <br/><br/>
673
+ <a id="e280"></a>
674
+
675
+ ## 🧑‍💻 sly is by e280
676
+ reward us with github stars
677
+ build with us at https://e280.org/ but only if you're cool
990
678