@e280/sly 0.2.0-0 → 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 (187) hide show
  1. package/README.md +414 -97
  2. package/package.json +4 -4
  3. package/s/demo/demo.bundle.ts +10 -6
  4. package/s/demo/views/counter.ts +22 -17
  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 +73 -0
  14. package/s/dom/{register.ts → parts/register.ts} +2 -7
  15. package/s/dom/types.ts +40 -0
  16. package/s/index.html.ts +4 -2
  17. package/s/index.ts +10 -9
  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 +35 -17
  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 +9 -5
  42. package/x/demo/demo.bundle.js.map +1 -1
  43. package/x/demo/demo.bundle.min.js +19 -17
  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 -15
  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 +32 -0
  72. package/x/dom/dom.js +54 -0
  73. package/x/dom/dom.js.map +1 -0
  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 -0
  78. package/x/index.d.ts +9 -8
  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 -8
  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 +12 -5
  110. package/x/{views → ui/base}/use.js +24 -10
  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/dom/dollar.ts +0 -27
  153. package/s/views/attributes.ts +0 -89
  154. package/s/views/types.ts +0 -40
  155. package/s/views/view.ts +0 -150
  156. package/x/dom/dashify.js.map +0 -1
  157. package/x/dom/dollar.d.ts +0 -10
  158. package/x/dom/dollar.js +0 -18
  159. package/x/dom/dollar.js.map +0 -1
  160. package/x/dom/register.js.map +0 -1
  161. package/x/views/attributes.d.ts +0 -10
  162. package/x/views/attributes.js +0 -46
  163. package/x/views/attributes.js.map +0 -1
  164. package/x/views/css-reset.js.map +0 -1
  165. package/x/views/types.d.ts +0 -31
  166. package/x/views/use.js.map +0 -1
  167. package/x/views/utils/apply-attrs.d.ts +0 -2
  168. package/x/views/utils/apply-attrs.js.map +0 -1
  169. package/x/views/utils/apply-styles.js.map +0 -1
  170. package/x/views/utils/mounts.js.map +0 -1
  171. package/x/views/view.d.ts +0 -9
  172. package/x/views/view.js +0 -116
  173. package/x/views/view.js.map +0 -1
  174. /package/s/dom/{dashify.ts → parts/dashify.ts} +0 -0
  175. /package/s/{views → ui/base}/css-reset.ts +0 -0
  176. /package/s/{views → ui/base}/utils/apply-styles.ts +0 -0
  177. /package/s/{views → ui/base}/utils/mounts.ts +0 -0
  178. /package/x/dom/{dashify.d.ts → parts/dashify.d.ts} +0 -0
  179. /package/x/dom/{dashify.js → parts/dashify.js} +0 -0
  180. /package/x/dom/{register.js → parts/register.js} +0 -0
  181. /package/x/{views → ui/base}/css-reset.d.ts +0 -0
  182. /package/x/{views → ui/base}/css-reset.js +0 -0
  183. /package/x/{views → ui/base}/utils/apply-styles.d.ts +0 -0
  184. /package/x/{views → ui/base}/utils/apply-styles.js +0 -0
  185. /package/x/{views → ui/base}/utils/mounts.d.ts +0 -0
  186. /package/x/{views → ui/base}/utils/mounts.js +0 -0
  187. /package/x/{views → ui}/types.js +0 -0
package/README.md CHANGED
@@ -1,63 +1,67 @@
1
1
 
2
2
  <div align="center"><img alt="" width="256" src="./assets/favicon.png"/></div>
3
3
 
4
- # 🦝 sly — mischievous shadow views
5
- > testing page https://sly.e280.org/
4
+ # 🦝 sly
5
+ > *mischievous shadow views*
6
6
 
7
- - 🍋 web app view library with taste
8
- - 🥷 leverage shadow-dom and slots
9
- - 🤯 register any view as a web component
10
- - 💲 handy little dom multitool
11
- - 🫛 ops for fancy loading spinners
12
- - 🧙‍♂️ took many years to get it right
13
- - 🌅 sly is the successor that replaces [@benev/slate](https://github.com/benevolent-games/slate)
14
- - 🧑‍💻 project by [@e280](https://e280.org/)
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))*
15
8
 
16
- <br/>
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/
15
+
16
+
17
+
18
+ <br/><br/>
17
19
 
18
20
  ## 🦝 sly and friends
19
21
 
20
22
  ```sh
21
- npm install @e280/sly lit
23
+ npm install @e280/sly lit @e280/strata @e280/stz
22
24
  ```
23
25
 
24
26
  > [!NOTE]
25
- > - 🔥 [lit](https://lit.dev/) for html rendering
26
- > - ⛏️ [@e280/strata](https://github.com/e280/strata) for state management (signals, state trees)
27
- > - 🏂 [@e280/stz](https://github.com/e280/stz) ***(optional)*** stz is our ts standard library
28
- > - 🐢 [scute](https://github.com/e280/scute) ***(optional)*** is our buildy-bundly-buddy
27
+ > - 🔥 [lit](https://lit.dev/), for html rendering
28
+ > - ⛏️ [@e280/strata](https://github.com/e280/strata), for state management (signals, state trees)
29
+ > - 🏂 [@e280/stz](https://github.com/e280/stz), our ts standard library
30
+ > - 🐢 [@e280/scute](https://github.com/e280/scute), our buildy-bundly-buddy
31
+
32
+
29
33
 
30
- <br/>
34
+ <br/><br/>
35
+ <a id="views"></a>
31
36
 
32
- ## 🦝 sly views
37
+ ## 🦝🍋 sly views and components
33
38
  > *views are the crown jewel of sly.. shadow-dom'd.. hooks-based.. "ergonomics"..*
34
39
 
35
40
  ```ts
36
41
  view(use => () => html`<p>hello world</p>`)
37
42
  ```
38
43
 
39
- - 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)
40
- - 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)
41
46
  - views are typescript-native and comfy for webdevs building apps
42
47
  - views automatically rerender whenever any [strata-compatible](https://github.com/e280/strata) state changes
43
48
 
44
49
  ### 🍋 view example
45
- - **import stuff**
46
- ```ts
47
- import {$, view} from "@e280/sly"
48
- import {html, css} from "lit"
49
- ```
50
+ ```ts
51
+ import {view, dom, BaseElement} from "@e280/sly"
52
+ import {html, css} from "lit"
53
+ ```
50
54
  - **declare a view**
51
55
  ```ts
52
56
  export const CounterView = view(use => (start: number) => {
53
57
  use.name("counter")
54
58
  use.styles(css`p {color: green}`)
55
59
 
56
- const count = use.signal(start)
57
- const increment = () => { count.value++ }
60
+ const $count = use.signal(start)
61
+ const increment = () => $count.value++
58
62
 
59
63
  return html`
60
- <p>count ${count()}</p>
64
+ <span>${$count.value}</span>
61
65
  <button @click="${increment}">+</button>
62
66
  `
63
67
  })
@@ -65,16 +69,21 @@ view(use => () => html`<p>hello world</p>`)
65
69
  - each view renders into a `<sly-view view="counter">` host (where "counter" is the `use.name` you provided)
66
70
  - **inject a view into the dom**
67
71
  ```ts
68
- $.render($(".app"), html`
69
- <h1>my cool counter demo</h1>
70
-
72
+ dom.in(".app").render(html`
73
+ <h1>cool counter demo</h1>
71
74
  ${CounterView(1)}
72
75
  `)
73
76
  ```
74
77
  - 🤯 **register a view as a web component**
75
78
  ```ts
76
- $.register({MyCounter: CounterView.component(1)})
77
- // <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>
78
87
  ```
79
88
 
80
89
  ### 🍋 view declaration settings
@@ -82,7 +91,7 @@ view(use => () => html`<p>hello world</p>`)
82
91
  ```ts
83
92
  export const CoolView = view
84
93
  .settings({mode: "open", delegatesFocus: true})
85
- .declare(use => (greeting: string) => {
94
+ .render(use => (greeting: string) => {
86
95
  return html`😎 ${greeting} <slot></slot>`
87
96
  })
88
97
  ```
@@ -92,9 +101,8 @@ view(use => () => html`<p>hello world</p>`)
92
101
  ### 🍋 view injection options
93
102
  - options for views at the template injection site
94
103
  ```ts
95
- $.render($(".app"), html`
96
- <h2>super cool example</h2>
97
-
104
+ dom.in(".app").render(html`
105
+ <h2>cool example</h2>
98
106
  ${CoolView.props("hello")
99
107
  .attr("class", "hero")
100
108
  .children(html`<em>spongebob</em>`)
@@ -106,27 +114,77 @@ view(use => () => html`<p>hello world</p>`)
106
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)
107
115
  - `render` — end the view chain and render the lit directive
108
116
 
109
- ### 🍋 web components
110
- - **build a component directly**
117
+ ### 🍋 view/component universality
118
+ - **you can start with a view,**
111
119
  ```ts
112
- 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")
113
126
  ```
114
- - notice that direct components don't take props (do `use.attrs` instead)
115
- - **convert any view into a web component**
127
+ then convert it to a component.
116
128
  ```ts
117
- 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>
118
137
  ```
119
- - to convert a view to a component, you provide props
120
- - 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
+ ```
121
182
  - **register web components to the dom**
122
183
  ```ts
123
- $.register({MyComponent, MyCounter})
124
- // <my-component></my-component>
125
- // <my-counter></my-counter>
184
+ dom.register({GreeterComponent})
126
185
  ```
127
- - `$.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
128
186
 
129
- ### 🍋 view "use" hooks reference
187
+ ### 🍋 "use" hooks reference
130
188
  - 👮 **follow the hooks rules**
131
189
  > just like [react hooks](https://react.dev/warnings/invalid-hook-call-warning), the execution order of sly's `use` hooks actually matters..
132
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..*
@@ -138,16 +196,26 @@ view(use => () => html`<p>hello world</p>`)
138
196
  ```ts
139
197
  use.styles(css1, css2, css3)
140
198
  ```
199
+ *(alias `use.css`)*
141
200
  - **use.signal** — create a [strata signal](https://github.com/e280/strata)
142
201
  ```ts
143
- const count = use.signal(1)
202
+ const $count = use.signal(1)
144
203
 
145
204
  // read the signal
146
- count() // 1
205
+ $count()
147
206
 
148
207
  // write the signal
149
- count(2)
150
- ```
208
+ $count(2)
209
+ ```
210
+ - `derived` signals
211
+ ```ts
212
+ const $product = use.derived(() => $count() * $whatever())
213
+ ```
214
+ - `lazy` signals
215
+ ```ts
216
+ const $product = use.lazy(() => $count() * $whatever())
217
+ ```
218
+ - go read the [strata readme](https://github.com/e280/strata) about this stuff
151
219
  - **use.once** — run fn at initialization, and return a value
152
220
  ```ts
153
221
  const whatever = use.once(() => {
@@ -186,9 +254,10 @@ view(use => () => html`<p>hello world</p>`)
186
254
 
187
255
  v // 123
188
256
  ```
189
- - **use.attrs** — ergonomic typed html attribute access
257
+ - **use.attrs** — ergonomic typed html attribute access
258
+ *(see [dom.attrs](#dom.attrs) for more details)*
190
259
  ```ts
191
- const attrs = use.attrs({
260
+ const attrs = use.attrs.spec({
192
261
  name: String,
193
262
  count: Number,
194
263
  active: Boolean,
@@ -199,14 +268,6 @@ view(use => () => html`<p>hello world</p>`)
199
268
  attrs.count // 123
200
269
  attrs.active // true
201
270
  ```
202
- ```ts
203
- attrs.name = "zenky"
204
- attrs.count = 124
205
- attrs.active = false // removes html attr
206
- ```
207
- ```ts
208
- attrs.name = undefined // removes the attr
209
- ```
210
271
  - **use.render** — rerender the view (debounced)
211
272
  ```ts
212
273
  use.render()
@@ -234,17 +295,17 @@ view(use => () => html`<p>hello world</p>`)
234
295
  const op = use.op.promise(doAsyncWork())
235
296
  ```
236
297
 
237
- ### 🍋 view "use" recipes
298
+ ### 🍋 "use" recipes
238
299
  - make a ticker — mount, repeat, and nap
239
300
  ```ts
240
301
  import {repeat, nap} from "@e280/stz"
241
302
  ```
242
303
  ```ts
243
- const seconds = use.signal(0)
304
+ const $seconds = use.signal(0)
244
305
 
245
306
  use.mount(() => repeat(async() => {
246
307
  await nap(1000)
247
- seconds.value++
308
+ $seconds.value++
248
309
  }))
249
310
  ```
250
311
  - wake + rendered, to do something after each mount's first render
@@ -254,55 +315,182 @@ view(use => () => html`<p>hello world</p>`)
254
315
  }))
255
316
  ```
256
317
 
257
- <br/>
258
318
 
259
- ## 🦝 sly dom multitool
260
- > *"it's not jquery!"*
261
319
 
262
- ### 💲 follow the money
263
- - import the dollarsign
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**
264
339
  ```ts
265
- import {$} from "@e280/sly"
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"
266
396
  ```
267
397
 
268
- ### 💲 dom queries
269
- - require an element
398
+
399
+
400
+ <br/><br/>
401
+ <a id="dom"></a>
402
+
403
+ ## 🦝🪄 sly dom
404
+ > *the "it's not jquery!" multitool*
405
+
406
+ ```ts
407
+ import {dom} from "@e280/sly"
408
+ ```
409
+
410
+ ### 🪄 dom queries
411
+ - `require` an element
270
412
  ```ts
271
- $(".demo")
272
- // HTMLElement (or throws error)
413
+ dom(".demo")
414
+ // HTMLElement (or throws)
273
415
  ```
274
- - request an element
416
+ - `maybe` get an element
275
417
  ```ts
276
- $.maybe(".demo")
418
+ dom.maybe(".demo")
277
419
  // HTMLElement | undefined
278
420
  ```
279
- - query all elements
421
+ - `select` all elements
280
422
  ```ts
281
- for (const item of $.all("ul li"))
282
- console.log(item)
423
+ dom.all(".demo ul li")
424
+ // HTMLElement[]
283
425
  ```
284
- - specify what element to query under
426
+ - `in` the scope of an element
285
427
  ```ts
286
- $("li", listElement)
287
- // HTMLElement
428
+ dom(element).require("li")
429
+ // HTMLElement (or throws)
430
+ ```
431
+ ```ts
432
+ dom(element).maybe("li")
433
+ // HTMLElement | undefined
288
434
  ```
289
-
290
- ### 💲 dom utilities
291
- - render content into an element
292
435
  ```ts
293
- $.render(element, html`<p>hello world</p>`)
436
+ dom(element).all("li")
437
+ // HTMLElement[]
294
438
  ```
295
- - register web components
439
+
440
+ ### 🪄 dom utilities
441
+ - `register` web components
296
442
  ```ts
297
- $.register({MyComponent, AnotherCoolComponent})
443
+ dom.register({MyComponent, AnotherCoolComponent})
298
444
  // <my-component>
299
445
  // <another-cool-component>
300
446
  ```
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
+ ```
455
+ ```ts
456
+ dom.render(element, html`<p>hello world</p>`)
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
+ ```
466
+ ```ts
467
+ attrs.name // "chase"
468
+ attrs.count // 123
469
+ attrs.active // true
470
+ ```
471
+ ```ts
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
485
+ ```
486
+
487
+
301
488
 
302
- <br/>
489
+ <br/><br/>
490
+ <a id="ops"></a>
303
491
 
304
- ## 🦝 sly ops, pods, and loaders
305
- > *async operations and displaying loading spinners.*
492
+ ## 🦝🫛 sly ops
493
+ > *tools for async operations and loading spinners*
306
494
 
307
495
  ```ts
308
496
  import {nap} from "@e280/stz"
@@ -356,7 +544,7 @@ import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
356
544
  ```
357
545
  - 🔥 create an op that calls and tracks an async fn
358
546
  ```ts
359
- const op = Op.fn(async() => {
547
+ const op = Op.load(async() => {
360
548
  await nap(4000)
361
549
  return 123
362
550
  })
@@ -426,9 +614,138 @@ import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
426
614
  - when the op is in error, the error will be displayed
427
615
  - when the op is ready, your fn is called and given the value
428
616
 
429
- <br/>
430
617
 
431
- ## 🧑‍💻 sly by e280
618
+
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>
743
+
744
+ ## 🦝🧑‍💻 sly is by e280
432
745
  reward us with github stars
433
746
  build with us at https://e280.org/ but only if you're cool
434
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-0",
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-0",
19
+ "@e280/strata": "^0.2.0-8",
20
20
  "@e280/stz": "^0.2.0"
21
21
  },
22
22
  "devDependencies": {
23
- "@e280/science": "^0.1.1",
24
- "@e280/scute": "^0.0.0",
23
+ "@e280/science": "^0.1.2",
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"