@e280/sly 0.2.0-1 → 0.2.0-10

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 (183) hide show
  1. package/README.md +348 -60
  2. package/package.json +3 -3
  3. package/s/demo/demo.bundle.ts +9 -5
  4. package/s/demo/views/counter.ts +22 -24
  5. package/s/demo/views/demo.ts +10 -6
  6. package/s/demo/views/fastcount.ts +29 -0
  7. package/s/demo/views/loaders.ts +2 -2
  8. package/s/dom/attrs/attrs.ts +21 -0
  9. package/s/dom/attrs/parts/attr-fns.ts +38 -0
  10. package/s/dom/attrs/parts/attr-proxies.ts +35 -0
  11. package/s/dom/attrs/parts/attr-spec.ts +29 -0
  12. package/s/dom/attrs/parts/on-attrs.ts +8 -0
  13. package/s/dom/dom.ts +38 -16
  14. package/s/dom/{register.ts → parts/register.ts} +2 -7
  15. package/s/dom/types.ts +39 -2
  16. package/s/index.html.ts +4 -2
  17. package/s/index.ts +10 -8
  18. package/s/loot/drag-and-drops.ts +82 -0
  19. package/s/loot/drops.ts +35 -0
  20. package/s/loot/helpers.ts +31 -0
  21. package/s/loot/index.ts +5 -0
  22. package/s/ops/loaders/make-loader.ts +3 -3
  23. package/s/ops/loaders/parts/anims.ts +1 -1
  24. package/s/ops/loaders/parts/ascii-anim.ts +3 -3
  25. package/s/ops/loaders/parts/error-display.ts +2 -2
  26. package/s/ops/op.ts +2 -2
  27. package/s/{views → ui/base}/use.ts +19 -19
  28. package/s/ui/base/utils/attr-watcher.ts +22 -0
  29. package/s/ui/base/utils/reactor.ts +21 -0
  30. package/s/ui/base-element.ts +76 -0
  31. package/s/ui/types.ts +33 -0
  32. package/s/ui/view/make-component.ts +33 -0
  33. package/s/ui/view/make-view.ts +40 -0
  34. package/s/{views/utils → ui/view/parts}/apply-attrs.ts +4 -7
  35. package/s/ui/view/parts/capsule.ts +67 -0
  36. package/s/ui/view/parts/chain.ts +33 -0
  37. package/s/ui/view/parts/context.ts +10 -0
  38. package/s/ui/view/parts/directive.ts +29 -0
  39. package/s/ui/view/parts/sly-view.ts +15 -0
  40. package/s/ui/view.ts +24 -0
  41. package/x/demo/demo.bundle.js +8 -4
  42. package/x/demo/demo.bundle.js.map +1 -1
  43. package/x/demo/demo.bundle.min.js +19 -22
  44. package/x/demo/demo.bundle.min.js.map +4 -4
  45. package/x/demo/views/counter.d.ts +374 -1
  46. package/x/demo/views/counter.js +19 -22
  47. package/x/demo/views/counter.js.map +1 -1
  48. package/x/demo/views/demo.d.ts +4 -1
  49. package/x/demo/views/demo.js +10 -5
  50. package/x/demo/views/demo.js.map +1 -1
  51. package/x/demo/views/fastcount.d.ts +12 -0
  52. package/x/demo/views/fastcount.js +21 -0
  53. package/x/demo/views/fastcount.js.map +1 -0
  54. package/x/demo/views/loaders.js +2 -2
  55. package/x/demo/views/loaders.js.map +1 -1
  56. package/x/dom/attrs/attrs.d.ts +20 -0
  57. package/x/dom/attrs/attrs.js +17 -0
  58. package/x/dom/attrs/attrs.js.map +1 -0
  59. package/x/dom/attrs/parts/attr-fns.d.ts +13 -0
  60. package/x/dom/attrs/parts/attr-fns.js +42 -0
  61. package/x/dom/attrs/parts/attr-fns.js.map +1 -0
  62. package/x/dom/attrs/parts/attr-proxies.d.ts +8 -0
  63. package/x/dom/attrs/parts/attr-proxies.js +21 -0
  64. package/x/dom/attrs/parts/attr-proxies.js.map +1 -0
  65. package/x/dom/attrs/parts/attr-spec.d.ts +3 -0
  66. package/x/dom/attrs/parts/attr-spec.js +21 -0
  67. package/x/dom/attrs/parts/attr-spec.js.map +1 -0
  68. package/x/dom/attrs/parts/on-attrs.d.ts +2 -0
  69. package/x/dom/attrs/parts/on-attrs.js +7 -0
  70. package/x/dom/attrs/parts/on-attrs.js.map +1 -0
  71. package/x/dom/dom.d.ts +18 -7
  72. package/x/dom/dom.js +25 -12
  73. package/x/dom/dom.js.map +1 -1
  74. package/x/dom/parts/dashify.js.map +1 -0
  75. package/x/dom/{register.d.ts → parts/register.d.ts} +2 -6
  76. package/x/dom/parts/register.js.map +1 -0
  77. package/x/dom/types.d.ts +14 -2
  78. package/x/index.d.ts +9 -7
  79. package/x/index.html +6 -4
  80. package/x/index.html.js +4 -2
  81. package/x/index.html.js.map +1 -1
  82. package/x/index.js +9 -7
  83. package/x/index.js.map +1 -1
  84. package/x/loot/drag-and-drops.d.ts +30 -0
  85. package/x/loot/drag-and-drops.js +63 -0
  86. package/x/loot/drag-and-drops.js.map +1 -0
  87. package/x/loot/drops.d.ts +14 -0
  88. package/x/loot/drops.js +25 -0
  89. package/x/loot/drops.js.map +1 -0
  90. package/x/loot/helpers.d.ts +3 -0
  91. package/x/loot/helpers.js +21 -0
  92. package/x/loot/helpers.js.map +1 -0
  93. package/x/loot/index.d.ts +3 -0
  94. package/x/loot/index.js +4 -0
  95. package/x/loot/index.js.map +1 -0
  96. package/x/ops/loaders/make-loader.d.ts +1 -1
  97. package/x/ops/loaders/make-loader.js +2 -2
  98. package/x/ops/loaders/make-loader.js.map +1 -1
  99. package/x/ops/loaders/parts/anims.d.ts +1 -1
  100. package/x/ops/loaders/parts/ascii-anim.d.ts +2 -2
  101. package/x/ops/loaders/parts/ascii-anim.js +2 -2
  102. package/x/ops/loaders/parts/ascii-anim.js.map +1 -1
  103. package/x/ops/loaders/parts/error-display.js +2 -2
  104. package/x/ops/loaders/parts/error-display.js.map +1 -1
  105. package/x/ops/op.d.ts +2 -2
  106. package/x/ops/op.js +2 -2
  107. package/x/ops/op.js.map +1 -1
  108. package/x/ui/base/css-reset.js.map +1 -0
  109. package/x/{views → ui/base}/use.d.ts +6 -6
  110. package/x/{views → ui/base}/use.js +10 -12
  111. package/x/ui/base/use.js.map +1 -0
  112. package/x/ui/base/utils/apply-styles.js.map +1 -0
  113. package/x/ui/base/utils/attr-watcher.d.ts +8 -0
  114. package/x/ui/base/utils/attr-watcher.js +20 -0
  115. package/x/ui/base/utils/attr-watcher.js.map +1 -0
  116. package/x/ui/base/utils/mounts.js.map +1 -0
  117. package/x/ui/base/utils/reactor.d.ts +5 -0
  118. package/x/ui/base/utils/reactor.js +17 -0
  119. package/x/ui/base/utils/reactor.js.map +1 -0
  120. package/x/ui/base-element.d.ts +19 -0
  121. package/x/ui/base-element.js +52 -0
  122. package/x/ui/base-element.js.map +1 -0
  123. package/x/ui/types.d.ts +20 -0
  124. package/x/{views → ui}/types.js.map +1 -1
  125. package/x/ui/view/make-component.d.ts +5 -0
  126. package/x/ui/view/make-component.js +16 -0
  127. package/x/ui/view/make-component.js.map +1 -0
  128. package/x/ui/view/make-view.d.ts +2 -0
  129. package/x/ui/view/make-view.js +16 -0
  130. package/x/ui/view/make-view.js.map +1 -0
  131. package/x/ui/view/parts/apply-attrs.d.ts +2 -0
  132. package/x/{views/utils → ui/view/parts}/apply-attrs.js +2 -4
  133. package/x/ui/view/parts/apply-attrs.js.map +1 -0
  134. package/x/ui/view/parts/capsule.d.ts +13 -0
  135. package/x/ui/view/parts/capsule.js +49 -0
  136. package/x/ui/view/parts/capsule.js.map +1 -0
  137. package/x/ui/view/parts/chain.d.ts +11 -0
  138. package/x/ui/view/parts/chain.js +21 -0
  139. package/x/ui/view/parts/chain.js.map +1 -0
  140. package/x/ui/view/parts/context.d.ts +8 -0
  141. package/x/ui/view/parts/context.js +10 -0
  142. package/x/ui/view/parts/context.js.map +1 -0
  143. package/x/ui/view/parts/directive.d.ts +5 -0
  144. package/x/ui/view/parts/directive.js +18 -0
  145. package/x/ui/view/parts/directive.js.map +1 -0
  146. package/x/ui/view/parts/sly-view.d.ts +5 -0
  147. package/x/ui/view/parts/sly-view.js +13 -0
  148. package/x/ui/view/parts/sly-view.js.map +1 -0
  149. package/x/ui/view.d.ts +11 -0
  150. package/x/ui/view.js +15 -0
  151. package/x/ui/view.js.map +1 -0
  152. package/s/views/attributes.ts +0 -89
  153. package/s/views/types.ts +0 -40
  154. package/s/views/view.ts +0 -150
  155. package/x/dom/dashify.js.map +0 -1
  156. package/x/dom/register.js.map +0 -1
  157. package/x/views/attributes.d.ts +0 -10
  158. package/x/views/attributes.js +0 -46
  159. package/x/views/attributes.js.map +0 -1
  160. package/x/views/css-reset.js.map +0 -1
  161. package/x/views/types.d.ts +0 -31
  162. package/x/views/use.js.map +0 -1
  163. package/x/views/utils/apply-attrs.d.ts +0 -2
  164. package/x/views/utils/apply-attrs.js.map +0 -1
  165. package/x/views/utils/apply-styles.js.map +0 -1
  166. package/x/views/utils/mounts.js.map +0 -1
  167. package/x/views/view.d.ts +0 -9
  168. package/x/views/view.js +0 -116
  169. package/x/views/view.js.map +0 -1
  170. /package/s/dom/{dashify.ts → parts/dashify.ts} +0 -0
  171. /package/s/{views → ui/base}/css-reset.ts +0 -0
  172. /package/s/{views → ui/base}/utils/apply-styles.ts +0 -0
  173. /package/s/{views → ui/base}/utils/mounts.ts +0 -0
  174. /package/x/dom/{dashify.d.ts → parts/dashify.d.ts} +0 -0
  175. /package/x/dom/{dashify.js → parts/dashify.js} +0 -0
  176. /package/x/dom/{register.js → parts/register.js} +0 -0
  177. /package/x/{views → ui/base}/css-reset.d.ts +0 -0
  178. /package/x/{views → ui/base}/css-reset.js +0 -0
  179. /package/x/{views → ui/base}/utils/apply-styles.d.ts +0 -0
  180. /package/x/{views → ui/base}/utils/apply-styles.js +0 -0
  181. /package/x/{views → ui/base}/utils/mounts.d.ts +0 -0
  182. /package/x/{views → ui/base}/utils/mounts.js +0 -0
  183. /package/x/{views → ui}/types.js +0 -0
package/README.md CHANGED
@@ -4,13 +4,14 @@
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 shiny new [lit](https://lit.dev/)-based frontend lib for webdevs. *(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
+ - 🍋 [**views**](#views) — hooks-based, shadow-dom'd, componentizable
10
+ - 🪵 [**base element**](#base-element)for a more classical experience
11
+ - 🪄 [**dom**](#dom)the "it's not jquery" multitool
12
+ - 🫛 [**ops**](#ops)tools for async operations and loading spinners
13
+ - 🪙 [**loot**](#loot) — drag-and-drop facilities
14
+ - 🧪 testing page — https://sly.e280.org/
14
15
 
15
16
 
16
17
 
@@ -19,37 +20,37 @@ sly replaces its predecessor, [slate](https://github.com/benevolent-games/slate)
19
20
  ## 🦝 sly and friends
20
21
 
21
22
  ```sh
22
- npm install @e280/sly lit
23
+ npm install @e280/sly lit @e280/strata @e280/stz
23
24
  ```
24
25
 
25
26
  > [!NOTE]
26
- > - 🔥 [lit](https://lit.dev/) for html rendering
27
+ > - 🔥 [lit](https://lit.dev/), for html rendering
27
28
  > - ⛏️ [@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
29
+ > - 🏂 [@e280/stz](https://github.com/e280/stz), our ts standard library
30
+ > - 🐢 [@e280/scute](https://github.com/e280/scute), our buildy-bundly-buddy
30
31
 
31
32
 
32
33
 
33
34
  <br/><br/>
35
+ <a id="views"></a>
34
36
 
35
- ## 🦝🍋 sly views
37
+ ## 🦝🍋 sly views and components
36
38
  > *views are the crown jewel of sly.. shadow-dom'd.. hooks-based.. "ergonomics"..*
37
39
 
38
40
  ```ts
39
41
  view(use => () => html`<p>hello world</p>`)
40
42
  ```
41
43
 
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
+ - any view can be converted into a web component
45
+ - views are not [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) per se, 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)
44
46
  - views are typescript-native and comfy for webdevs building apps
45
47
  - views automatically rerender whenever any [strata-compatible](https://github.com/e280/strata) state changes
46
48
 
47
49
  ### 🍋 view example
48
- - **import stuff**
49
- ```ts
50
- import {view, dom} from "@e280/sly"
51
- import {html, css} from "lit"
52
- ```
50
+ ```ts
51
+ import {view, dom, BaseElement} from "@e280/sly"
52
+ import {html, css} from "lit"
53
+ ```
53
54
  - **declare a view**
54
55
  ```ts
55
56
  export const CounterView = view(use => (start: number) => {
@@ -60,7 +61,7 @@ view(use => () => html`<p>hello world</p>`)
60
61
  const increment = () => $count.value++
61
62
 
62
63
  return html`
63
- <p>count ${$count.value}</p>
64
+ <span>${$count.value}</span>
64
65
  <button @click="${increment}">+</button>
65
66
  `
66
67
  })
@@ -75,8 +76,14 @@ view(use => () => html`<p>hello world</p>`)
75
76
  ```
76
77
  - 🤯 **register a view as a web component**
77
78
  ```ts
78
- dom.register({MyCounter: CounterView.component(1)})
79
- // <my-counter></my-counter>
79
+ dom.register({
80
+ MyCounter: CounterView
81
+ .component(BaseElement)
82
+ .props(component => [dom.attrs(component).number.start ?? 0]),
83
+ })
84
+ ```
85
+ ```html
86
+ <my-counter start="1"></my-counter>
80
87
  ```
81
88
 
82
89
  ### 🍋 view declaration settings
@@ -84,7 +91,7 @@ view(use => () => html`<p>hello world</p>`)
84
91
  ```ts
85
92
  export const CoolView = view
86
93
  .settings({mode: "open", delegatesFocus: true})
87
- .declare(use => (greeting: string) => {
94
+ .render(use => (greeting: string) => {
88
95
  return html`😎 ${greeting} <slot></slot>`
89
96
  })
90
97
  ```
@@ -107,27 +114,77 @@ view(use => () => html`<p>hello world</p>`)
107
114
  - `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)
108
115
  - `render` — end the view chain and render the lit directive
109
116
 
110
- ### 🍋 view web components
111
- - **build a component directly**
117
+ ### 🍋 view/component universality
118
+ - **you can start with a view,**
112
119
  ```ts
113
- const MyComponent = view.component(use => html`<p>hello world</p>`)
120
+ export const GreeterView = view(use => (name: string) => {
121
+ return html`<p>hello ${name}</p>`
122
+ })
123
+
124
+ // view usage:
125
+ // GreeterView("pimsley")
114
126
  ```
115
- - notice that direct components don't take props (do `use.attrs` instead)
116
- - **convert any view into a web component**
127
+ then convert it to a component.
117
128
  ```ts
118
- const MyCounter = CounterView.component(1)
129
+ export class GreeterComponent extends (
130
+ GreeterView
131
+ .component(BaseElement)
132
+ .props(component => [component.getAttribute("name") ?? "unknown"])
133
+ ) {}
134
+
135
+ // html usage:
136
+ // <greeter-component name="pimsley"></greeter-component>
119
137
  ```
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
138
+ - this trick with `class` and `extends` is amazing because typescript exports both the value of your component class, but also its type (doesn't work if you use `const`)
139
+ - **you can start with a component,**
140
+ ```ts
141
+ export class GreeterComponent extends (
142
+ view(use => (name: string) => {
143
+ return html`<p>hello ${name}</p>`
144
+ })
145
+ .component(BaseElement)
146
+ .props(component => [component.getAttribute("name") ?? "unknown"])
147
+ ) {}
148
+
149
+ // html usage:
150
+ // <greeter-component name="pimsley"></greeter-component>
151
+ ```
152
+ then it already has a `.view` ready for you.
153
+ ```ts
154
+ // view usage:
155
+ // GreeterComponent.view("pimsley")
156
+ ```
157
+ - **understanding `.component(C)` and `.props(fn)`**
158
+ - `.props` takes a fn that is called every render, which returns the props given to the view
159
+ ```ts
160
+ .component(BaseElement)
161
+ .props(() => ["pimsley"])
162
+ ```
163
+ the props fn receives the component instance, so you can query html attributes or instance properties
164
+ ```ts
165
+ .component(BaseElement)
166
+ .props(component => [component.getAttribute("name") ?? "unknown"])
167
+ ```
168
+ - `.component` accepts a subclass of `BaseElement`, which lets you define your own properties and methods for your component class
169
+ ```ts
170
+ .component(class extends BaseElement {
171
+ $name = signal("jim raynor")
172
+ updateName(name: string) {
173
+ this.$name.value = name
174
+ }
175
+ })
176
+ .props(component => [component.$name.value])
177
+ ```
178
+ - `.component` lets devs interacting with your component get nice types
179
+ ```ts
180
+ dom<GreeterComponent>("my-component").updateName("mortimer")
181
+ ```
122
182
  - **register web components to the dom**
123
183
  ```ts
124
- dom.register({MyComponent, MyCounter})
125
- // <my-component></my-component>
126
- // <my-counter></my-counter>
184
+ dom.register({GreeterComponent})
127
185
  ```
128
- - `dom.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
129
186
 
130
- ### 🍋 view "use" hooks reference
187
+ ### 🍋 "use" hooks reference
131
188
  - 👮 **follow the hooks rules**
132
189
  > just like [react hooks](https://react.dev/warnings/invalid-hook-call-warning), the execution order of sly's `use` hooks actually matters..
133
190
  > 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 +207,9 @@ view(use => () => html`<p>hello world</p>`)
150
207
  // write the signal
151
208
  $count(2)
152
209
  ```
153
- - `derive` signals
210
+ - `derived` signals
154
211
  ```ts
155
- const $product = use.derive(() => $count() * $whatever())
212
+ const $product = use.derived(() => $count() * $whatever())
156
213
  ```
157
214
  - `lazy` signals
158
215
  ```ts
@@ -197,9 +254,10 @@ view(use => () => html`<p>hello world</p>`)
197
254
 
198
255
  v // 123
199
256
  ```
200
- - **use.attrs** — ergonomic typed html attribute access
257
+ - **use.attrs** — ergonomic typed html attribute access
258
+ *(see [dom.attrs](#dom.attrs) for more details)*
201
259
  ```ts
202
- const attrs = use.attrs({
260
+ const attrs = use.attrs.spec({
203
261
  name: String,
204
262
  count: Number,
205
263
  active: Boolean,
@@ -210,14 +268,6 @@ view(use => () => html`<p>hello world</p>`)
210
268
  attrs.count // 123
211
269
  attrs.active // true
212
270
  ```
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
220
- ```
221
271
  - **use.render** — rerender the view (debounced)
222
272
  ```ts
223
273
  use.render()
@@ -245,7 +295,7 @@ view(use => () => html`<p>hello world</p>`)
245
295
  const op = use.op.promise(doAsyncWork())
246
296
  ```
247
297
 
248
- ### 🍋 view "use" recipes
298
+ ### 🍋 "use" recipes
249
299
  - make a ticker — mount, repeat, and nap
250
300
  ```ts
251
301
  import {repeat, nap} from "@e280/stz"
@@ -268,6 +318,87 @@ view(use => () => html`<p>hello world</p>`)
268
318
 
269
319
 
270
320
  <br/><br/>
321
+ <a id="base-element"></a>
322
+
323
+ ## 🦝🪵 sly base element
324
+ > *the classic experience*
325
+
326
+ ```ts
327
+ import {BaseElement, Use, dom} from "@e280/sly"
328
+ import {html, css} from "lit"
329
+ ```
330
+
331
+ `BaseElement` is a class-based approach to create a custom element web component.
332
+
333
+ it lets you expose js properties on the element instance, which helps you setup a better developer experience for people interacting with your element through the dom.
334
+
335
+ base element enjoys the same `use` hooks as views.
336
+
337
+ ### 🪵 base element setup
338
+ - **declare your element class**
339
+ ```ts
340
+ export class MyElement extends BaseElement {
341
+ static styles = css`span{color:orange}`
342
+
343
+ // custom property
344
+ start = 10
345
+
346
+ // custom attributes
347
+ attrs = dom.attrs(this).spec({
348
+ multiply: Number,
349
+ })
350
+
351
+ // custom methods
352
+ hello() {
353
+ return "world"
354
+ }
355
+
356
+ render(use: Use) {
357
+ const $count = use.signal(1)
358
+ const increment = () => $count.value++
359
+
360
+ const {start} = this
361
+ const {multiply = 1} = this.attrs
362
+ const result = start + (multiply * $count())
363
+
364
+ return html`
365
+ <span>${result}</span>
366
+ <button @click="${increment}">+</button>
367
+ `
368
+ }
369
+ }
370
+ ```
371
+ - **register your element to the dom**
372
+ ```ts
373
+ dom.register({MyElement})
374
+ ```
375
+
376
+ ### 🪵 base element usage
377
+ - **place the element in your html body**
378
+ ```html
379
+ <body>
380
+ <my-element></my-element>
381
+ </body>
382
+ ```
383
+ - **now you can interact with it**
384
+ ```ts
385
+ const myElement = dom<MyElement>("my-element")
386
+
387
+ // js property
388
+ myElement.start = 100
389
+
390
+ // html attributes
391
+ myElement.attrs.multiply = 2
392
+
393
+ // methods
394
+ myElement.hello()
395
+ // "world"
396
+ ```
397
+
398
+
399
+
400
+ <br/><br/>
401
+ <a id="dom"></a>
271
402
 
272
403
  ## 🦝🪄 sly dom
273
404
  > *the "it's not jquery!" multitool*
@@ -277,56 +408,86 @@ import {dom} from "@e280/sly"
277
408
  ```
278
409
 
279
410
  ### 🪄 dom queries
280
- - require an element
411
+ - `require` an element
281
412
  ```ts
282
413
  dom(".demo")
283
414
  // HTMLElement (or throws)
284
415
  ```
285
- - maybe get an element
416
+ - `maybe` get an element
286
417
  ```ts
287
418
  dom.maybe(".demo")
288
419
  // HTMLElement | undefined
289
420
  ```
290
- - select all elements
421
+ - `select` all elements
291
422
  ```ts
292
423
  dom.all(".demo ul li")
293
424
  // HTMLElement[]
294
425
  ```
295
- - within a specific container
426
+ - `in` the scope of an element
296
427
  ```ts
297
- dom.in(element).require("li")
428
+ dom(element).require("li")
298
429
  // HTMLElement (or throws)
299
430
  ```
300
431
  ```ts
301
- dom.in(element).maybe("li")
432
+ dom(element).maybe("li")
302
433
  // HTMLElement | undefined
303
434
  ```
304
435
  ```ts
305
- dom.in(element).all("li")
436
+ dom(element).all("li")
306
437
  // HTMLElement[]
307
438
  ```
308
439
 
309
440
  ### 🪄 dom utilities
310
- - register web components
441
+ - `register` web components
311
442
  ```ts
312
443
  dom.register({MyComponent, AnotherCoolComponent})
313
444
  // <my-component>
314
445
  // <another-cool-component>
315
446
  ```
316
- - render content into an element
447
+ - `dom.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
448
+ - `render` content into an element
449
+ ```ts
450
+ dom(element).render(html`<p>hello world</p>`)
451
+ ```
452
+ ```ts
453
+ dom.in(".demo").render(html`<p>hello world</p>`)
454
+ ```
317
455
  ```ts
318
456
  dom.render(element, html`<p>hello world</p>`)
319
457
  ```
458
+ - `attrs` <a id="dom.attrs"></a> to setup a type-happy html attribute helper
459
+ ```ts
460
+ const attrs = dom.attrs(element).spec({
461
+ name: String,
462
+ count: Number,
463
+ active: Boolean,
464
+ })
465
+ ```
320
466
  ```ts
321
- dom.in(element).render(html`<p>hello world</p>`)
467
+ attrs.name // "chase"
468
+ attrs.count // 123
469
+ attrs.active // true
322
470
  ```
323
471
  ```ts
324
- dom.in(".demo").render(html`<p>hello world</p>`)
472
+ attrs.name = "zenky"
473
+ attrs.count = 124
474
+ attrs.active = false // removes html attr
475
+ ```
476
+ ```ts
477
+ attrs.name = undefined // removes the attr
478
+ attrs.count = undefined // removes the attr
479
+ ```
480
+ or if you wanna be more loosey-goosy, skip the spec
481
+ ```ts
482
+ dom.attrs(element).string.name = "pimsley"
483
+ dom.attrs(element).number.count = 125
484
+ dom.attrs(element).boolean.active = true
325
485
  ```
326
486
 
327
487
 
328
488
 
329
489
  <br/><br/>
490
+ <a id="ops"></a>
330
491
 
331
492
  ## 🦝🫛 sly ops
332
493
  > *tools for async operations and loading spinners*
@@ -383,7 +544,7 @@ import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
383
544
  ```
384
545
  - 🔥 create an op that calls and tracks an async fn
385
546
  ```ts
386
- const op = Op.fn(async() => {
547
+ const op = Op.load(async() => {
387
548
  await nap(4000)
388
549
  return 123
389
550
  })
@@ -456,8 +617,135 @@ import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
456
617
 
457
618
 
458
619
  <br/><br/>
620
+ <a id="loot"></a>
621
+
622
+ ## 🦝🪙 loot
623
+ > *drag-and-drop facilities*
624
+
625
+ ```ts
626
+ import {loot, view, dom} from "@e280/sly"
627
+ import {ev} from "@e280/stz"
628
+ ```
629
+
630
+ ### 🪙 `loot.Drops`
631
+ > *accept the user dropping stuff like files onto the page*
632
+ - **setup drops**
633
+ ```ts
634
+ const drops = new loot.Drops({
635
+ predicate: loot.hasFiles,
636
+ acceptDrop: event => {
637
+ const files = loot.files(event)
638
+ console.log("files dropped", files)
639
+ },
640
+ })
641
+ ```
642
+ - **attach event listeners to your dropzone,** one of these ways:
643
+ - **view example**
644
+ ```ts
645
+ view(() => () => html`
646
+ <div
647
+ ?data-indicator="${drops.$indicator()}"
648
+ @dragover="${drops.dragover}"
649
+ @dragleave="${drops.dragleave}"
650
+ @drop="${drops.drop}">
651
+ my dropzone
652
+ </div>
653
+ `)
654
+ ```
655
+ - **vanilla-js whole-page example**
656
+ ```ts
657
+ // attach listeners to the body
658
+ ev(document.body, {
659
+ dragover: drops.dragover,
660
+ dragleave: drops.dragleave,
661
+ drop: drops.drop,
662
+ })
663
+
664
+ // sly attribute handler for the body
665
+ const attrs = dom.attrs(document.body).spec({
666
+ "data-indicator": Boolean,
667
+ })
668
+
669
+ // sync the data-indicator attribute
670
+ drops.$indicator.on(bool => attrs["data-indicator"] = bool)
671
+ ```
672
+ - **flashy css indicator for the dropzone,** so the user knows your app is eager to accept the drop
673
+ ```css
674
+ [data-indicator] {
675
+ border: 0.5em dashed cyan;
676
+ }
677
+ ```
678
+
679
+ ### 🪙 `loot.DragAndDrops`
680
+ > *setup drag-and-drops between items within your page*
681
+ - **declare types for your draggy and droppy things**
682
+ ```ts
683
+ // money that can be picked up and dragged
684
+ type Money = {value: number}
685
+ // dnd will call this a "draggy"
686
+
687
+ // bag that money can be dropped into
688
+ type Bag = {id: number}
689
+ // dnd will call this a "droppy"
690
+ ```
691
+ - **make your dnd**
692
+ ```ts
693
+ const dnd = new loot.DragAndDrops<Money, Bag>({
694
+ acceptDrop: (event, money, bag) => {
695
+ console.log("drop!", {money, bag})
696
+ },
697
+ })
698
+ ```
699
+ - **attach dragzone listeners** (there can be many dragzones...)
700
+ ```ts
701
+ view(use => () => {
702
+ const money = use.once((): Money => ({value: 280}))
703
+ const dragzone = use.once(() => dnd.dragzone(() => money))
704
+
705
+ return html`
706
+ <div
707
+ draggable="${dragzone.draggable}"
708
+ @dragstart="${dragzone.dragstart}"
709
+ @dragend="${dragzone.dragend}">
710
+ money ${money.value}
711
+ </div>
712
+ `
713
+ })
714
+ ```
715
+ - **attach dropzone listeners** (there can be many dropzones...)
716
+ ```ts
717
+ view(use => () => {
718
+ const bag = use.once((): Bag => ({id: 1}))
719
+ const dropzone = use.once(() => dnd.dropzone(() => bag))
720
+ const indicator = !!(dnd.dragging && dnd.hovering === bag)
721
+
722
+ return html`
723
+ <div
724
+ ?data-indicator="${indicator}"
725
+ @dragenter="${dropzone.dragenter}"
726
+ @dragleave="${dropzone.dragleave}"
727
+ @dragover="${dropzone.dragover}"
728
+ @drop="${dropzone.drop}">
729
+ bag ${bag.id}
730
+ </div>
731
+ `
732
+ })
733
+ ```
734
+
735
+ ### 🪙 loot helpers
736
+ - **`loot.hasFiles(event)`** — return true if `DragEvent` contains any files (useful in `predicate`)
737
+ - **`loot.files(event)`** — returns an array of files in a drop's `DragEvent` (useful in `acceptDrop`)
738
+
739
+
740
+
741
+ <br/><br/>
742
+ <a id="e280"></a>
459
743
 
460
744
  ## 🦝🧑‍💻 sly is by e280
461
745
  reward us with github stars
462
746
  build with us at https://e280.org/ but only if you're cool
463
747
 
748
+
749
+
750
+ <br/><br/>
751
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e280/sly",
3
- "version": "0.2.0-1",
3
+ "version": "0.2.0-10",
4
4
  "description": "web shadow views",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,12 +16,12 @@
16
16
  "lit": "^3.3.1"
17
17
  },
18
18
  "dependencies": {
19
- "@e280/strata": "^0.2.0-5",
19
+ "@e280/strata": "^0.2.0-8",
20
20
  "@e280/stz": "^0.2.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@e280/science": "^0.1.2",
24
- "@e280/scute": "^0.0.0",
24
+ "@e280/scute": "^0.1.0",
25
25
  "http-server": "^14.1.1",
26
26
  "npm-run-all": "^4.1.5",
27
27
  "typescript": "^5.9.2"
@@ -1,10 +1,14 @@
1
1
 
2
2
  import {dom} from "../dom/dom.js"
3
- import {DemoView} from "./views/demo.js"
4
- import {CounterView} from "./views/counter.js"
5
-
6
- dom.in(".demo").render(DemoView())
7
- dom.register({DemoCounter: CounterView.component(1)})
3
+ import {CounterComponent} from "./views/counter.js"
4
+ import {DemoComponent} from "./views/demo.js"
5
+ import {FastcountElement} from "./views/fastcount.js"
6
+
7
+ dom.register({
8
+ DemoComponent,
9
+ CounterComponent,
10
+ FastcountElement,
11
+ })
8
12
 
9
13
  console.log("🦝 sly")
10
14
 
@@ -1,44 +1,42 @@
1
1
 
2
2
  import {css, html} from "lit"
3
- import {repeat} from "@e280/stz"
4
3
 
5
- import {view} from "../../views/view.js"
6
- import {cssReset} from "../../views/css-reset.js"
4
+ import {dom} from "../../dom/dom.js"
5
+ import {view} from "../../ui/view.js"
6
+ import {cssReset} from "../../ui/base/css-reset.js"
7
+ import {BaseElement} from "../../ui/base-element.js"
7
8
 
8
- export const CounterView = view(use => (initial: number) => {
9
+ export const CounterView = view(use => (start: number, step: number) => {
9
10
  use.name("counter")
10
11
  use.styles(cssReset, styles)
11
12
 
12
- const $seconds = use.signal(0)
13
- const start = use.once(() => Date.now())
14
- use.mount(() => repeat(async() => {
15
- const since = Date.now() - start
16
- $seconds.set(Math.floor(since / 1000))
17
- }))
18
-
19
- const $count = use.signal(initial)
20
- const increment = () => $count.value++
21
-
22
- const $product = use.signal
23
- .derive(() => $count() * $seconds())
13
+ const $count = use.signal(start)
14
+ const increment = () => { $count.value += step }
24
15
 
25
16
  return html`
26
17
  <slot></slot>
27
18
  <div>
28
- <span>${$seconds.get()}</span>
29
- </div>
30
- <div>
31
- <span>${$count.get()}</span>
19
+ <span>${$count()}</span>
32
20
  </div>
33
21
  <div>
34
- <span>${$product.get()}</span>
35
- </div>
36
- <div>
37
- <button @click="${increment}">+</button>
22
+ <button @click="${increment}">++</button>
38
23
  </div>
39
24
  `
40
25
  })
41
26
 
27
+
28
+ // convert a view into a web component
29
+ export class CounterComponent extends (
30
+ CounterView
31
+ .component(class extends BaseElement {
32
+ attrs = dom.attrs(this).spec({
33
+ start: Number,
34
+ step: Number,
35
+ })
36
+ })
37
+ .props(c => [c.attrs.start ?? 0, c.attrs.step ?? 1])
38
+ ) {}
39
+
42
40
  const styles = css`
43
41
  :host {
44
42
  display: flex;