@e280/sly 0.2.0-3 → 0.2.0-31

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 (304) hide show
  1. package/README.md +624 -97
  2. package/package.json +13 -6
  3. package/s/base/element.ts +76 -0
  4. package/s/base/index.ts +6 -0
  5. package/s/{views → base}/use.ts +25 -16
  6. package/s/base/utils/attr-watcher.ts +22 -0
  7. package/s/base/utils/reactor.ts +32 -0
  8. package/s/base/utils/states.ts +49 -0
  9. package/s/base/utils/use-attrs.ts +36 -0
  10. package/s/demo/demo.bundle.ts +9 -5
  11. package/s/demo/views/counter.ts +21 -24
  12. package/s/demo/views/demo.ts +10 -6
  13. package/s/demo/views/fastcount.ts +29 -0
  14. package/s/demo/views/loaders.ts +7 -7
  15. package/s/dom/attrs/attrs.ts +21 -0
  16. package/s/dom/attrs/parts/attr-fns.ts +68 -0
  17. package/s/dom/attrs/parts/attr-proxies.ts +35 -0
  18. package/s/dom/attrs/parts/attr-spec.ts +29 -0
  19. package/s/dom/attrs/parts/on-attrs.ts +8 -0
  20. package/s/dom/dom.ts +22 -38
  21. package/s/dom/index.ts +4 -0
  22. package/s/dom/parts/dom-scope.ts +46 -0
  23. package/s/dom/parts/el.ts +14 -0
  24. package/s/dom/parts/elmer.ts +38 -0
  25. package/s/dom/parts/eve.ts +24 -0
  26. package/s/dom/parts/mk.ts +9 -0
  27. package/s/dom/parts/queries.ts +26 -0
  28. package/s/dom/{register.ts → parts/register.ts} +2 -7
  29. package/s/dom/types.ts +42 -0
  30. package/s/index.html.ts +4 -2
  31. package/s/index.ts +7 -19
  32. package/s/loaders/index.barrel.ts +10 -0
  33. package/s/loaders/index.ts +4 -0
  34. package/s/loaders/make.ts +14 -0
  35. package/s/loaders/mock.ts +11 -0
  36. package/s/{ops/loaders → loaders}/parts/anims.ts +1 -1
  37. package/s/{ops/loaders → loaders}/parts/ascii-anim.ts +6 -5
  38. package/s/{ops/loaders → loaders}/parts/error-display.ts +2 -2
  39. package/s/loaders/types.ts +6 -0
  40. package/s/loot/drag-and-drops.ts +82 -0
  41. package/s/loot/{drop.ts → drops.ts} +8 -17
  42. package/s/loot/helpers.ts +3 -3
  43. package/s/loot/index.barrel.ts +5 -0
  44. package/s/loot/index.ts +2 -3
  45. package/s/ops/index.ts +5 -0
  46. package/s/ops/op.ts +1 -0
  47. package/s/spa/index.barrel.ts +6 -0
  48. package/s/spa/index.ts +4 -0
  49. package/s/spa/plumbing/braces.ts +76 -0
  50. package/s/spa/plumbing/primitives.ts +85 -0
  51. package/s/spa/plumbing/router-core.ts +49 -0
  52. package/s/spa/plumbing/types.ts +45 -0
  53. package/s/spa/router.ts +49 -0
  54. package/s/spa/spa.test.ts +91 -0
  55. package/s/tests.test.ts +4 -1
  56. package/s/view/index.ts +7 -0
  57. package/s/view/types.ts +39 -0
  58. package/s/view/utils/contextualize.ts +45 -0
  59. package/s/view/utils/make-component.ts +34 -0
  60. package/s/view/utils/make-view.ts +48 -0
  61. package/s/view/utils/parts/capsule.ts +67 -0
  62. package/s/view/utils/parts/chain.ts +40 -0
  63. package/s/view/utils/parts/context.ts +11 -0
  64. package/s/view/utils/parts/directive.ts +29 -0
  65. package/s/view/utils/parts/sly-view.ts +15 -0
  66. package/s/view/view.ts +24 -0
  67. package/x/base/css-reset.js.map +1 -0
  68. package/x/base/element.d.ts +19 -0
  69. package/x/base/element.js +52 -0
  70. package/x/base/element.js.map +1 -0
  71. package/x/base/index.d.ts +4 -0
  72. package/x/base/index.js +5 -0
  73. package/x/base/index.js.map +1 -0
  74. package/x/{views → base}/use.d.ts +8 -4
  75. package/x/{views → base}/use.js +15 -9
  76. package/x/base/use.js.map +1 -0
  77. package/x/base/utils/apply-styles.js.map +1 -0
  78. package/x/base/utils/attr-watcher.d.ts +8 -0
  79. package/x/base/utils/attr-watcher.js +20 -0
  80. package/x/base/utils/attr-watcher.js.map +1 -0
  81. package/x/base/utils/mounts.js.map +1 -0
  82. package/x/base/utils/reactor.d.ts +5 -0
  83. package/x/base/utils/reactor.js +25 -0
  84. package/x/base/utils/reactor.js.map +1 -0
  85. package/x/base/utils/states.d.ts +13 -0
  86. package/x/base/utils/states.js +41 -0
  87. package/x/base/utils/states.js.map +1 -0
  88. package/x/base/utils/use-attrs.d.ts +11 -0
  89. package/x/base/utils/use-attrs.js +18 -0
  90. package/x/base/utils/use-attrs.js.map +1 -0
  91. package/x/demo/demo.bundle.js +8 -4
  92. package/x/demo/demo.bundle.js.map +1 -1
  93. package/x/demo/demo.bundle.min.js +19 -22
  94. package/x/demo/demo.bundle.min.js.map +4 -4
  95. package/x/demo/views/counter.d.ts +374 -1
  96. package/x/demo/views/counter.js +19 -22
  97. package/x/demo/views/counter.js.map +1 -1
  98. package/x/demo/views/demo.d.ts +4 -1
  99. package/x/demo/views/demo.js +10 -5
  100. package/x/demo/views/demo.js.map +1 -1
  101. package/x/demo/views/fastcount.d.ts +12 -0
  102. package/x/demo/views/fastcount.js +21 -0
  103. package/x/demo/views/fastcount.js.map +1 -0
  104. package/x/demo/views/loaders.js +6 -6
  105. package/x/demo/views/loaders.js.map +1 -1
  106. package/x/dom/attrs/attrs.d.ts +23 -0
  107. package/x/dom/attrs/attrs.js +17 -0
  108. package/x/dom/attrs/attrs.js.map +1 -0
  109. package/x/dom/attrs/parts/attr-fns.d.ts +16 -0
  110. package/x/dom/attrs/parts/attr-fns.js +64 -0
  111. package/x/dom/attrs/parts/attr-fns.js.map +1 -0
  112. package/x/dom/attrs/parts/attr-proxies.d.ts +8 -0
  113. package/x/dom/attrs/parts/attr-proxies.js +21 -0
  114. package/x/dom/attrs/parts/attr-proxies.js.map +1 -0
  115. package/x/dom/attrs/parts/attr-spec.d.ts +3 -0
  116. package/x/dom/attrs/parts/attr-spec.js +21 -0
  117. package/x/dom/attrs/parts/attr-spec.js.map +1 -0
  118. package/x/dom/attrs/parts/on-attrs.d.ts +2 -0
  119. package/x/dom/attrs/parts/on-attrs.js +7 -0
  120. package/x/dom/attrs/parts/on-attrs.js.map +1 -0
  121. package/x/dom/dom.d.ts +15 -16
  122. package/x/dom/dom.js +21 -34
  123. package/x/dom/dom.js.map +1 -1
  124. package/x/dom/index.d.ts +2 -0
  125. package/x/dom/index.js +3 -0
  126. package/x/dom/index.js.map +1 -0
  127. package/x/dom/parts/dashify.js.map +1 -0
  128. package/x/dom/parts/dom-scope.d.ts +15 -0
  129. package/x/dom/parts/dom-scope.js +35 -0
  130. package/x/dom/parts/dom-scope.js.map +1 -0
  131. package/x/dom/parts/el.d.ts +2 -0
  132. package/x/dom/parts/el.js +7 -0
  133. package/x/dom/parts/el.js.map +1 -0
  134. package/x/dom/parts/elmer.d.ts +11 -0
  135. package/x/dom/parts/elmer.js +32 -0
  136. package/x/dom/parts/elmer.js.map +1 -0
  137. package/x/dom/parts/eve.d.ts +7 -0
  138. package/x/dom/parts/eve.js +16 -0
  139. package/x/dom/parts/eve.js.map +1 -0
  140. package/x/dom/parts/mk.d.ts +2 -0
  141. package/x/dom/parts/mk.js +7 -0
  142. package/x/dom/parts/mk.js.map +1 -0
  143. package/x/dom/parts/queries.d.ts +4 -0
  144. package/x/dom/parts/queries.js +13 -0
  145. package/x/dom/parts/queries.js.map +1 -0
  146. package/x/dom/{register.d.ts → parts/register.d.ts} +2 -6
  147. package/x/dom/parts/register.js.map +1 -0
  148. package/x/dom/types.d.ts +15 -0
  149. package/x/index.d.ts +7 -16
  150. package/x/index.html +6 -4
  151. package/x/index.html.js +4 -2
  152. package/x/index.html.js.map +1 -1
  153. package/x/index.js +7 -16
  154. package/x/index.js.map +1 -1
  155. package/x/loaders/index.barrel.d.ts +7 -0
  156. package/x/loaders/index.barrel.js +7 -0
  157. package/x/loaders/index.barrel.js.map +1 -0
  158. package/x/loaders/index.d.ts +2 -0
  159. package/x/loaders/index.js +2 -0
  160. package/x/loaders/index.js.map +1 -0
  161. package/x/loaders/make.d.ts +3 -0
  162. package/x/loaders/make.js +6 -0
  163. package/x/loaders/make.js.map +1 -0
  164. package/x/loaders/mock.d.ts +2 -0
  165. package/x/loaders/mock.js +8 -0
  166. package/x/loaders/mock.js.map +1 -0
  167. package/x/{ops/loaders → loaders}/parts/anims.d.ts +1 -1
  168. package/x/loaders/parts/anims.js.map +1 -0
  169. package/x/{ops/loaders → loaders}/parts/ascii-anim.d.ts +2 -2
  170. package/x/{ops/loaders → loaders}/parts/ascii-anim.js +4 -4
  171. package/x/loaders/parts/ascii-anim.js.map +1 -0
  172. package/x/loaders/parts/error-display.d.ts +1 -0
  173. package/x/{ops/loaders → loaders}/parts/error-display.js +2 -2
  174. package/x/loaders/parts/error-display.js.map +1 -0
  175. package/x/loaders/types.d.ts +3 -0
  176. package/x/loaders/types.js.map +1 -0
  177. package/x/loot/drag-and-drops.d.ts +30 -0
  178. package/x/loot/drag-and-drops.js +63 -0
  179. package/x/loot/drag-and-drops.js.map +1 -0
  180. package/x/loot/{drop.d.ts → drops.d.ts} +3 -5
  181. package/x/loot/drops.js +25 -0
  182. package/x/loot/drops.js.map +1 -0
  183. package/x/loot/helpers.d.ts +3 -3
  184. package/x/loot/helpers.js +3 -3
  185. package/x/loot/helpers.js.map +1 -1
  186. package/x/loot/index.barrel.d.ts +3 -0
  187. package/x/loot/index.barrel.js +4 -0
  188. package/x/loot/index.barrel.js.map +1 -0
  189. package/x/loot/index.d.ts +2 -3
  190. package/x/loot/index.js +1 -3
  191. package/x/loot/index.js.map +1 -1
  192. package/x/ops/index.d.ts +3 -0
  193. package/x/ops/index.js +4 -0
  194. package/x/ops/index.js.map +1 -0
  195. package/x/ops/op.js +1 -0
  196. package/x/ops/op.js.map +1 -1
  197. package/x/spa/index.barrel.d.ts +4 -0
  198. package/x/spa/index.barrel.js +3 -0
  199. package/x/spa/index.barrel.js.map +1 -0
  200. package/x/spa/index.d.ts +2 -0
  201. package/x/spa/index.js +2 -0
  202. package/x/spa/index.js.map +1 -0
  203. package/x/spa/plumbing/braces.d.ts +12 -0
  204. package/x/spa/plumbing/braces.js +55 -0
  205. package/x/spa/plumbing/braces.js.map +1 -0
  206. package/x/spa/plumbing/primitives.d.ts +22 -0
  207. package/x/spa/plumbing/primitives.js +65 -0
  208. package/x/spa/plumbing/primitives.js.map +1 -0
  209. package/x/spa/plumbing/router-core.d.ts +13 -0
  210. package/x/spa/plumbing/router-core.js +38 -0
  211. package/x/spa/plumbing/router-core.js.map +1 -0
  212. package/x/spa/plumbing/types.d.ts +35 -0
  213. package/x/spa/plumbing/types.js +2 -0
  214. package/x/spa/plumbing/types.js.map +1 -0
  215. package/x/spa/router.d.ts +16 -0
  216. package/x/spa/router.js +39 -0
  217. package/x/spa/router.js.map +1 -0
  218. package/x/spa/spa.test.d.ts +15 -0
  219. package/x/spa/spa.test.js +78 -0
  220. package/x/spa/spa.test.js.map +1 -0
  221. package/x/tests.test.js +4 -1
  222. package/x/tests.test.js.map +1 -1
  223. package/x/view/index.d.ts +5 -0
  224. package/x/view/index.js +6 -0
  225. package/x/view/index.js.map +1 -0
  226. package/x/view/types.d.ts +21 -0
  227. package/x/view/types.js +2 -0
  228. package/x/{views → view}/types.js.map +1 -1
  229. package/x/view/utils/contextualize.d.ts +13 -0
  230. package/x/view/utils/contextualize.js +18 -0
  231. package/x/view/utils/contextualize.js.map +1 -0
  232. package/x/view/utils/make-component.d.ts +5 -0
  233. package/x/view/utils/make-component.js +17 -0
  234. package/x/view/utils/make-component.js.map +1 -0
  235. package/x/view/utils/make-view.d.ts +2 -0
  236. package/x/view/utils/make-view.js +24 -0
  237. package/x/view/utils/make-view.js.map +1 -0
  238. package/x/view/utils/parts/capsule.d.ts +13 -0
  239. package/x/view/utils/parts/capsule.js +49 -0
  240. package/x/view/utils/parts/capsule.js.map +1 -0
  241. package/x/view/utils/parts/chain.d.ts +13 -0
  242. package/x/view/utils/parts/chain.js +26 -0
  243. package/x/view/utils/parts/chain.js.map +1 -0
  244. package/x/view/utils/parts/context.d.ts +9 -0
  245. package/x/view/utils/parts/context.js +10 -0
  246. package/x/view/utils/parts/context.js.map +1 -0
  247. package/x/view/utils/parts/directive.d.ts +5 -0
  248. package/x/view/utils/parts/directive.js +18 -0
  249. package/x/view/utils/parts/directive.js.map +1 -0
  250. package/x/view/utils/parts/sly-view.d.ts +5 -0
  251. package/x/view/utils/parts/sly-view.js +13 -0
  252. package/x/view/utils/parts/sly-view.js.map +1 -0
  253. package/x/view/view.d.ts +11 -0
  254. package/x/view/view.js +15 -0
  255. package/x/view/view.js.map +1 -0
  256. package/s/loot/drag-drop.ts +0 -76
  257. package/s/ops/loaders/make-loader.ts +0 -18
  258. package/s/views/attributes.ts +0 -89
  259. package/s/views/types.ts +0 -40
  260. package/s/views/utils/apply-attrs.ts +0 -33
  261. package/s/views/view.ts +0 -150
  262. package/x/dom/dashify.js.map +0 -1
  263. package/x/dom/register.js.map +0 -1
  264. package/x/loot/drag-drop.d.ts +0 -29
  265. package/x/loot/drag-drop.js +0 -54
  266. package/x/loot/drag-drop.js.map +0 -1
  267. package/x/loot/drop.js +0 -32
  268. package/x/loot/drop.js.map +0 -1
  269. package/x/ops/loaders/make-loader.d.ts +0 -5
  270. package/x/ops/loaders/make-loader.js +0 -7
  271. package/x/ops/loaders/make-loader.js.map +0 -1
  272. package/x/ops/loaders/parts/anims.js.map +0 -1
  273. package/x/ops/loaders/parts/ascii-anim.js.map +0 -1
  274. package/x/ops/loaders/parts/error-display.d.ts +0 -1
  275. package/x/ops/loaders/parts/error-display.js.map +0 -1
  276. package/x/views/attributes.d.ts +0 -10
  277. package/x/views/attributes.js +0 -46
  278. package/x/views/attributes.js.map +0 -1
  279. package/x/views/css-reset.js.map +0 -1
  280. package/x/views/types.d.ts +0 -31
  281. package/x/views/use.js.map +0 -1
  282. package/x/views/utils/apply-attrs.d.ts +0 -2
  283. package/x/views/utils/apply-attrs.js +0 -21
  284. package/x/views/utils/apply-attrs.js.map +0 -1
  285. package/x/views/utils/apply-styles.js.map +0 -1
  286. package/x/views/utils/mounts.js.map +0 -1
  287. package/x/views/view.d.ts +0 -9
  288. package/x/views/view.js +0 -116
  289. package/x/views/view.js.map +0 -1
  290. /package/s/{views → base}/css-reset.ts +0 -0
  291. /package/s/{views → base}/utils/apply-styles.ts +0 -0
  292. /package/s/{views → base}/utils/mounts.ts +0 -0
  293. /package/s/dom/{dashify.ts → parts/dashify.ts} +0 -0
  294. /package/x/{views → base}/css-reset.d.ts +0 -0
  295. /package/x/{views → base}/css-reset.js +0 -0
  296. /package/x/{views → base}/utils/apply-styles.d.ts +0 -0
  297. /package/x/{views → base}/utils/apply-styles.js +0 -0
  298. /package/x/{views → base}/utils/mounts.d.ts +0 -0
  299. /package/x/{views → base}/utils/mounts.js +0 -0
  300. /package/x/dom/{dashify.d.ts → parts/dashify.d.ts} +0 -0
  301. /package/x/dom/{dashify.js → parts/dashify.js} +0 -0
  302. /package/x/dom/{register.js → parts/register.js} +0 -0
  303. /package/x/{ops/loaders → loaders}/parts/anims.js +0 -0
  304. /package/x/{views → loaders}/types.js +0 -0
package/README.md CHANGED
@@ -4,99 +4,116 @@
4
4
  # 🦝 sly
5
5
  > *mischievous shadow views*
6
6
 
7
- [@e280](https://e280.org/)'s shiny, tasteful, incredible new [lit](https://lit.dev/)-based toolkit for frontend web developers.
8
- sly replaces its predecessor, [slate](https://github.com/benevolent-games/slate).
7
+ [@e280](https://e280.org/)'s new [lit](https://lit.dev/)-based frontend webdev library. *(sly replaces its predecessor, [slate](https://github.com/benevolent-games/slate))*
9
8
 
10
- - 🍋 **views** hooks-based, shadow-dom'd, componentizable
11
- - 🪄 **dom**the "it's not jquery" multitool
12
- - 🫛 **ops**tools for async operations and loading spinners
13
- - 🧪 **testing page** https://sly.e280.org/
9
+ - **✨[shiny](https://shiny.e280.org/)✨**our wip component library https://shiny.e280.org/
10
+ - 🍋 [**#views**](#views)shadow-dom'd, hooks-based, componentizable
11
+ - 🪵 [**#base-element**](#base-element) — for a more classical experience
12
+ - 🪄 [**#dom**](#dom)the "it's not jquery" multitool
13
+ - 🫛 [**#ops**](#ops) — reactive tooling for async operations
14
+ - ⏳ [**#loaders**](#loaders) — animated loading spinners for rendering ops
15
+ - 💅 [**#spa**](#spa) — hash routing for your spa-day
16
+ - 🪙 [**#loot**](#loot) — drag-and-drop facilities
17
+ - 🧪 testing page — https://sly.e280.org/
14
18
 
15
19
 
16
20
 
17
21
  <br/><br/>
18
22
 
19
23
  ## 🦝 sly and friends
24
+ > `@e280/sly`
20
25
 
21
26
  ```sh
22
- npm install @e280/sly lit
27
+ npm install @e280/sly lit @e280/strata @e280/stz
23
28
  ```
24
29
 
25
30
  > [!NOTE]
26
- > - 🔥 [lit](https://lit.dev/) for html rendering
31
+ > - 🔥 [lit](https://lit.dev/), for html rendering
27
32
  > - ⛏️ [@e280/strata](https://github.com/e280/strata), for state management (signals, state trees)
28
- > - 🏂 [@e280/stz](https://github.com/e280/stz) is our ts standard library
29
- > - 🐢 [scute](https://github.com/e280/scute) is our buildy-bundly-buddy
33
+ > - 🏂 [@e280/stz](https://github.com/e280/stz), our ts standard library
34
+ > - 🐢 [@e280/scute](https://github.com/e280/scute), our buildy-bundly-buddy
35
+
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...
30
39
 
31
40
 
32
41
 
33
42
  <br/><br/>
43
+ <a id="views"></a>
34
44
 
35
- ## 🦝🍋 sly views
36
- > *views are the crown jewel of sly.. shadow-dom'd.. hooks-based.. "ergonomics"..*
45
+ ## 🍋🦝 sly views
46
+ > `@e280/sly/view`
47
+ > *the crown jewel of sly*
37
48
 
38
49
  ```ts
39
50
  view(use => () => html`<p>hello world</p>`)
40
51
  ```
41
52
 
42
- - views are not [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components), but they do have [shadow roots](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) and support [slots](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots)
43
- - any view can be registered as a web component, perfect for entrypoints or sharing widgets with html authors
44
- - views are typescript-native and comfy for webdevs building apps
45
- - views automatically rerender whenever any [strata-compatible](https://github.com/e280/strata) state changes
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)
46
59
 
47
60
  ### 🍋 view example
48
- - **import stuff**
49
- ```ts
50
- import {view, dom} from "@e280/sly"
51
- import {html, css} from "lit"
52
- ```
53
- - **declare a view**
61
+ ```ts
62
+ import {view, dom, BaseElement} from "@e280/sly"
63
+ import {html, css} from "lit"
64
+ ```
65
+ - **declare view**
54
66
  ```ts
55
67
  export const CounterView = view(use => (start: number) => {
56
- use.name("counter")
57
68
  use.styles(css`p {color: green}`)
58
69
 
59
70
  const $count = use.signal(start)
60
71
  const increment = () => $count.value++
61
72
 
62
73
  return html`
63
- <p>count ${$count.value}</p>
64
- <button @click="${increment}">+</button>
74
+ <button @click="${increment}">
75
+ ${$count.value}
76
+ </button>
65
77
  `
66
78
  })
67
79
  ```
68
- - each view renders into a `<sly-view view="counter">` host (where "counter" is the `use.name` you provided)
69
- - **inject a view into the dom**
80
+ - `$count` is a [strata signal](https://github.com/e280/strata#readme) *(we like those)*
81
+ - **inject view into dom**
70
82
  ```ts
71
83
  dom.in(".app").render(html`
72
84
  <h1>cool counter demo</h1>
73
85
  ${CounterView(1)}
74
86
  `)
75
87
  ```
76
- - 🤯 **register a view as a web component**
88
+ - 🤯 **register view as web component**
77
89
  ```ts
78
- dom.register({MyCounter: CounterView.component(1)})
79
- // <my-counter></my-counter>
90
+ dom.register({
91
+ MyCounter: CounterView
92
+ .component()
93
+ .props(() => [1]),
94
+ })
95
+ ```
96
+ ```html
97
+ <my-counter></my-counter>
80
98
  ```
81
99
 
82
- ### 🍋 view declaration settings
83
- - special settings for views at declaration-time
100
+ ### 🍋 view settings
101
+ - optional settings for views you should know about
84
102
  ```ts
85
103
  export const CoolView = view
86
104
  .settings({mode: "open", delegatesFocus: true})
87
- .declare(use => (greeting: string) => {
88
- return html`😎 ${greeting} <slot></slot>`
89
- })
105
+ .render(use => (greeting: string) => html`😎 ${greeting} <slot></slot>`)
90
106
  ```
91
107
  - all [attachShadow params](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#parameters) (like `mode` and `delegatesFocus`) are valid `settings`
92
108
  - note the `<slot></slot>` we'll use in the next example lol
93
109
 
94
- ### 🍋 view injection options
95
- - options for views at the template injection site
110
+ ### 🍋 view chains
111
+ - views have this sick chaining syntax for supplying more stuff at the template injection site
96
112
  ```ts
97
113
  dom.in(".app").render(html`
98
114
  <h2>cool example</h2>
99
- ${CoolView.props("hello")
115
+ ${CoolView
116
+ .props("hello")
100
117
  .attr("class", "hero")
101
118
  .children(html`<em>spongebob</em>`)
102
119
  .render()}
@@ -104,30 +121,102 @@ view(use => () => html`<p>hello world</p>`)
104
121
  ```
105
122
  - `props` — provide props and start a view chain
106
123
  - `attr` — set html attributes on the `<sly-view>` host element
107
- - `children` — nested content in the host element, can be [slotted](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots)
124
+ - `children` — add nested [slottable](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots) content
108
125
  - `render` — end the view chain and render the lit directive
109
126
 
110
- ### 🍋 view web components
111
- - **build a component directly**
127
+ ### 🍋 view/component universality
128
+ - **you can start with a view,**
112
129
  ```ts
113
- const MyComponent = view.component(use => html`<p>hello world</p>`)
130
+ export const GreeterView = view(use => (name: string) => {
131
+ return html`<p>hello ${name}</p>`
132
+ })
114
133
  ```
115
- - notice that direct components don't take props (do `use.attrs` instead)
116
- - **convert any view into a web component**
134
+ - view usage
135
+ ```ts
136
+ GreeterView("pimsley")
137
+ ```
138
+ **then you can convert it to a component.**
117
139
  ```ts
118
- const MyCounter = CounterView.component(1)
140
+ export class GreeterComponent extends (
141
+ GreeterView
142
+ .component()
143
+ .props(component => [component.getAttribute("name") ?? "unknown"])
144
+ ) {}
119
145
  ```
120
- - to convert a view to a component, you provide props
121
- - note that the component instance has a render method like `element.render(2)` which can take new props at runtime
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
+ ```
122
206
  - **register web components to the dom**
123
207
  ```ts
124
- dom.register({MyComponent, MyCounter})
125
- // <my-component></my-component>
126
- // <my-counter></my-counter>
208
+ dom.register({GreeterComponent})
127
209
  ```
128
- - `dom.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
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
+ })
215
+ ```
216
+
217
+ <a id="use"></a>
129
218
 
130
- ### 🍋 view "use" hooks reference
219
+ ### 🍋 "use" hooks reference
131
220
  - 👮 **follow the hooks rules**
132
221
  > just like [react hooks](https://react.dev/warnings/invalid-hook-call-warning), the execution order of sly's `use` hooks actually matters..
133
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..*
@@ -150,9 +239,9 @@ view(use => () => html`<p>hello world</p>`)
150
239
  // write the signal
151
240
  $count(2)
152
241
  ```
153
- - `derive` signals
242
+ - `derived` signals
154
243
  ```ts
155
- const $product = use.derive(() => $count() * $whatever())
244
+ const $product = use.derived(() => $count() * $whatever())
156
245
  ```
157
246
  - `lazy` signals
158
247
  ```ts
@@ -197,27 +286,46 @@ view(use => () => html`<p>hello world</p>`)
197
286
 
198
287
  v // 123
199
288
  ```
200
- - **use.attrs** — ergonomic typed html attribute access
289
+ - **use.events** — attach event listeners to the element (auto-cleaned up)
201
290
  ```ts
202
- const attrs = use.attrs({
203
- name: String,
204
- count: Number,
205
- active: Boolean,
291
+ use.events({
292
+ keydown: (e: KeyboardEvent) => console.log("keydown", e.code),
293
+ keyup: (e: KeyboardEvent) => console.log("keyup", e.code),
206
294
  })
207
295
  ```
296
+ - **use.states** — [internal states](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states) helper
208
297
  ```ts
209
- attrs.name // "chase"
210
- attrs.count // 123
211
- attrs.active // true
298
+ const states = use.states()
299
+ states.assign("active", "cool")
212
300
  ```
213
- ```ts
214
- attrs.name = "zenky"
215
- attrs.count = 124
216
- attrs.active = false // removes html attr
217
- ```
218
- ```ts
219
- attrs.name = undefined // removes the attr
301
+ ```css
302
+ [view="my-view"]::state(active) { color: yellow; }
303
+ [view="my-view"]::state(cool) { outline: 1px solid cyan; }
220
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
+ ```
221
329
  - **use.render** — rerender the view (debounced)
222
330
  ```ts
223
331
  use.render()
@@ -245,15 +353,15 @@ view(use => () => html`<p>hello world</p>`)
245
353
  const op = use.op.promise(doAsyncWork())
246
354
  ```
247
355
 
248
- ### 🍋 view "use" recipes
249
- - make a ticker — mount, repeat, and nap
356
+ ### 🍋 "use" recipes
357
+ - make a ticker — mount, cycle, and nap
250
358
  ```ts
251
- import {repeat, nap} from "@e280/stz"
359
+ import {cycle, nap} from "@e280/stz"
252
360
  ```
253
361
  ```ts
254
362
  const $seconds = use.signal(0)
255
363
 
256
- use.mount(() => repeat(async() => {
364
+ use.mount(() => cycle(async() => {
257
365
  await nap(1000)
258
366
  $seconds.value++
259
367
  }))
@@ -268,72 +376,259 @@ view(use => () => html`<p>hello world</p>`)
268
376
 
269
377
 
270
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>
271
477
 
272
- ## 🦝🪄 sly dom
273
- > *the "it's not jquery!" multitool*
478
+ ## 🪄🦝 sly dom
479
+ > `@e280/sly/dom`
480
+ > *the "it's not jquery!" multitool*
274
481
 
275
482
  ```ts
276
483
  import {dom} from "@e280/sly"
277
484
  ```
278
485
 
279
486
  ### 🪄 dom queries
280
- - require an element
487
+ - `require` an element
281
488
  ```ts
282
489
  dom(".demo")
283
490
  // HTMLElement (or throws)
284
491
  ```
285
- - maybe get an element
492
+ ```ts
493
+ // alias
494
+ dom.require(".demo")
495
+ // HTMLElement (or throws)
496
+ ```
497
+ - `maybe` get an element
286
498
  ```ts
287
499
  dom.maybe(".demo")
288
500
  // HTMLElement | undefined
289
501
  ```
290
- - select all elements
502
+ - `all` matching elements in an array
291
503
  ```ts
292
504
  dom.all(".demo ul li")
293
505
  // HTMLElement[]
294
506
  ```
295
- - within a specific container
507
+
508
+ ### 🪄 dom.in scope
509
+ - make a scope
296
510
  ```ts
297
- dom.in(element).require("li")
298
- // HTMLElement (or throws)
511
+ dom.in(".demo") // selector
512
+ // Dom instance
299
513
  ```
300
514
  ```ts
301
- dom.in(element).maybe("li")
302
- // HTMLElement | undefined
515
+ dom.in(demoElement) // element
516
+ // Dom instance
303
517
  ```
518
+ - run queries in that scope
304
519
  ```ts
305
- dom.in(element).all("li")
306
- // HTMLElement[]
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")
307
527
  ```
308
528
 
309
529
  ### 🪄 dom utilities
310
- - register web components
530
+ - `dom.register` web components
311
531
  ```ts
312
532
  dom.register({MyComponent, AnotherCoolComponent})
313
533
  // <my-component>
314
534
  // <another-cool-component>
315
535
  ```
316
- - render content into an element
536
+ - `dom.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
537
+ - `dom.render` content into an element
317
538
  ```ts
318
539
  dom.render(element, html`<p>hello world</p>`)
319
540
  ```
320
541
  ```ts
321
- dom.in(element).render(html`<p>hello world</p>`)
542
+ dom.in(".demo").render(html`<p>hello world</p>`)
322
543
  ```
544
+ - `dom.el` little element builder
323
545
  ```ts
324
- dom.in(".demo").render(html`<p>hello world</p>`)
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
325
618
  ```
326
619
 
327
620
 
328
621
 
329
622
  <br/><br/>
623
+ <a id="ops"></a>
330
624
 
331
- ## 🦝🫛 sly ops
332
- > *tools for async operations and loading spinners*
625
+ ## 🫛🦝 sly ops
626
+ > `@e280/sly/ops`
627
+ > *tools for async operations and loading spinners*
333
628
 
334
629
  ```ts
335
630
  import {nap} from "@e280/stz"
336
- import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
631
+ import {Pod, podium, Op, loaders} from "@e280/sly"
337
632
  ```
338
633
 
339
634
  ### 🫛 pods: loading/ready/error
@@ -432,14 +727,29 @@ import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
432
727
  - loading if any ops are in loading, otherwise
433
728
  - ready if all the ops are ready
434
729
 
435
- ### 🫛 loaders: animated loading spinners
436
- - create a `loader` using `makeLoader`
730
+
731
+
732
+ <br/><br/>
733
+ <a id="loaders"></a>
734
+
735
+ ## ⏳🦝 sly loaders
736
+ > `@e280/sly/loaders`
737
+ > *animated loading spinners for ops*
738
+
739
+ ```ts
740
+ import {loaders} from "@e280/sly"
741
+ ```
742
+
743
+ ### ⏳ make a loader, choose an anim
744
+ - create a loader fn
437
745
  ```ts
438
- const loader = makeLoader(anims.dots)
746
+ const loader = loaders.make(loaders.anims.dots)
439
747
  ```
440
748
  - see all the anims available on the testing page https://sly.e280.org/
441
749
  - ngl, i made too many.. *i was having fun, okay?*
442
- - use the loader to render your op
750
+
751
+ ### ⏳ render an op with it
752
+ - use your loader to render an op
443
753
  ```ts
444
754
  return html`
445
755
  <h2>cool stuff</h2>
@@ -456,8 +766,225 @@ import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
456
766
 
457
767
 
458
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
+ ```
459
778
 
460
- ## 🦝🧑‍💻 sly is by e280
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
+ <br/><br/>
858
+ <a id="loot"></a>
859
+
860
+ ## 🪙🦝 loot
861
+ > `@e280/sly/loot`
862
+ > *drag-and-drop facilities*
863
+
864
+ ```ts
865
+ import {loot, view, dom} from "@e280/sly"
866
+ import {ev} from "@e280/stz"
867
+ ```
868
+
869
+ ### 🪙 `loot.Drops`
870
+ > *accept the user dropping stuff like files onto the page*
871
+ - **setup drops**
872
+ ```ts
873
+ const drops = new loot.Drops({
874
+ predicate: loot.hasFiles,
875
+ acceptDrop: event => {
876
+ const files = loot.files(event)
877
+ console.log("files dropped", files)
878
+ },
879
+ })
880
+ ```
881
+ - **attach event listeners to your dropzone,** one of these ways:
882
+ - **view example**
883
+ ```ts
884
+ view(() => () => html`
885
+ <div
886
+ ?data-indicator="${drops.$indicator()}"
887
+ @dragover="${drops.dragover}"
888
+ @dragleave="${drops.dragleave}"
889
+ @drop="${drops.drop}">
890
+ my dropzone
891
+ </div>
892
+ `)
893
+ ```
894
+ - **vanilla-js whole-page example**
895
+ ```ts
896
+ // attach listeners to the body
897
+ ev(document.body, {
898
+ dragover: drops.dragover,
899
+ dragleave: drops.dragleave,
900
+ drop: drops.drop,
901
+ })
902
+
903
+ // sly attribute handler for the body
904
+ const attrs = dom.attrs(document.body).spec({
905
+ "data-indicator": Boolean,
906
+ })
907
+
908
+ // sync the data-indicator attribute
909
+ drops.$indicator.on(bool => attrs["data-indicator"] = bool)
910
+ ```
911
+ - **flashy css indicator for the dropzone,** so the user knows your app is eager to accept the drop
912
+ ```css
913
+ [data-indicator] {
914
+ border: 0.5em dashed cyan;
915
+ }
916
+ ```
917
+
918
+ ### 🪙 `loot.DragAndDrops`
919
+ > *setup drag-and-drops between items within your page*
920
+ - **declare types for your draggy and droppy things**
921
+ ```ts
922
+ // money that can be picked up and dragged
923
+ type Money = {value: number}
924
+ // dnd will call this a "draggy"
925
+
926
+ // bag that money can be dropped into
927
+ type Bag = {id: number}
928
+ // dnd will call this a "droppy"
929
+ ```
930
+ - **make your dnd**
931
+ ```ts
932
+ const dnd = new loot.DragAndDrops<Money, Bag>({
933
+ acceptDrop: (event, money, bag) => {
934
+ console.log("drop!", {money, bag})
935
+ },
936
+ })
937
+ ```
938
+ - **attach dragzone listeners** (there can be many dragzones...)
939
+ ```ts
940
+ view(use => () => {
941
+ const money = use.once((): Money => ({value: 280}))
942
+ const dragzone = use.once(() => dnd.dragzone(() => money))
943
+
944
+ return html`
945
+ <div
946
+ draggable="${dragzone.draggable}"
947
+ @dragstart="${dragzone.dragstart}"
948
+ @dragend="${dragzone.dragend}">
949
+ money ${money.value}
950
+ </div>
951
+ `
952
+ })
953
+ ```
954
+ - **attach dropzone listeners** (there can be many dropzones...)
955
+ ```ts
956
+ view(use => () => {
957
+ const bag = use.once((): Bag => ({id: 1}))
958
+ const dropzone = use.once(() => dnd.dropzone(() => bag))
959
+ const indicator = !!(dnd.dragging && dnd.hovering === bag)
960
+
961
+ return html`
962
+ <div
963
+ ?data-indicator="${indicator}"
964
+ @dragenter="${dropzone.dragenter}"
965
+ @dragleave="${dropzone.dragleave}"
966
+ @dragover="${dropzone.dragover}"
967
+ @drop="${dropzone.drop}">
968
+ bag ${bag.id}
969
+ </div>
970
+ `
971
+ })
972
+ ```
973
+
974
+ ### 🪙 loot helpers
975
+ - **`loot.hasFiles(event)`** — return true if `DragEvent` contains any files (useful in `predicate`)
976
+ - **`loot.files(event)`** — returns an array of files in a drop's `DragEvent` (useful in `acceptDrop`)
977
+
978
+
979
+
980
+ <br/><br/>
981
+ <a id="e280"></a>
982
+
983
+ ## 🧑‍💻🦝 sly is by e280
461
984
  reward us with github stars
462
985
  build with us at https://e280.org/ but only if you're cool
463
986
 
987
+
988
+
989
+ <br/><br/>
990
+