@e280/sly 0.0.0-7 → 0.0.0-9
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.
- package/README.md +257 -98
- package/package.json +1 -1
- package/s/demo/demo.bundle.ts +2 -5
- package/s/demo/views/counter.ts +2 -2
- package/s/demo/views/demo.ts +2 -2
- package/s/demo/views/loaders.ts +4 -4
- package/s/index.ts +15 -14
- package/s/{features/op → ops}/op.ts +15 -8
- package/s/{features/op → ops}/podium.ts +18 -18
- package/s/views/attributes.ts +89 -0
- package/s/{features/views → views}/use.ts +21 -5
- package/s/{features/views → views}/view.ts +15 -10
- package/x/demo/demo.bundle.js +2 -4
- package/x/demo/demo.bundle.js.map +1 -1
- package/x/demo/demo.bundle.min.js +12 -12
- package/x/demo/demo.bundle.min.js.map +4 -4
- package/x/demo/views/counter.js +2 -2
- package/x/demo/views/counter.js.map +1 -1
- package/x/demo/views/demo.js +2 -2
- package/x/demo/views/demo.js.map +1 -1
- package/x/demo/views/loaders.js +4 -4
- package/x/demo/views/loaders.js.map +1 -1
- package/x/dom/dashify.js.map +1 -0
- package/x/dom/dollar.js.map +1 -0
- package/x/dom/register.js.map +1 -0
- package/x/{features/op → dom}/types.js.map +1 -1
- package/x/index.d.ts +15 -14
- package/x/index.html +2 -2
- package/x/index.js +15 -14
- package/x/index.js.map +1 -1
- package/x/ops/loaders/make-loader.js.map +1 -0
- package/x/ops/loaders/parts/anims.js.map +1 -0
- package/x/ops/loaders/parts/ascii-anim.js.map +1 -0
- package/x/ops/loaders/parts/error-display.d.ts +1 -0
- package/x/ops/loaders/parts/error-display.js.map +1 -0
- package/x/{features/op → ops}/op.d.ts +6 -3
- package/x/{features/op → ops}/op.js +13 -7
- package/x/ops/op.js.map +1 -0
- package/x/ops/podium.d.ts +9 -0
- package/x/{features/op → ops}/podium.js +18 -18
- package/x/ops/podium.js.map +1 -0
- package/x/{features/dom → ops}/types.js.map +1 -1
- package/x/views/attributes.d.ts +10 -0
- package/x/views/attributes.js +46 -0
- package/x/views/attributes.js.map +1 -0
- package/x/views/css-reset.js.map +1 -0
- package/x/{features/views → views}/types.js.map +1 -1
- package/x/{features/views → views}/use.d.ts +9 -4
- package/x/{features/views → views}/use.js +21 -6
- package/x/views/use.js.map +1 -0
- package/x/views/utils/apply-attrs.js.map +1 -0
- package/x/views/utils/apply-styles.js.map +1 -0
- package/x/views/utils/mounts.js.map +1 -0
- package/x/{features/views → views}/view.d.ts +2 -3
- package/x/{features/views → views}/view.js +7 -7
- package/x/views/view.js.map +1 -0
- package/x/features/dom/dashify.js.map +0 -1
- package/x/features/dom/dollar.js.map +0 -1
- package/x/features/dom/register.js.map +0 -1
- package/x/features/op/loaders/make-loader.js.map +0 -1
- package/x/features/op/loaders/parts/anims.js.map +0 -1
- package/x/features/op/loaders/parts/ascii-anim.js.map +0 -1
- package/x/features/op/loaders/parts/error-display.d.ts +0 -1
- package/x/features/op/loaders/parts/error-display.js.map +0 -1
- package/x/features/op/op.js.map +0 -1
- package/x/features/op/podium.d.ts +0 -9
- package/x/features/op/podium.js.map +0 -1
- package/x/features/views/css-reset.js.map +0 -1
- package/x/features/views/use.js.map +0 -1
- package/x/features/views/utils/apply-attrs.js.map +0 -1
- package/x/features/views/utils/apply-styles.js.map +0 -1
- package/x/features/views/utils/mounts.js.map +0 -1
- package/x/features/views/view.js.map +0 -1
- /package/s/{features/dom → dom}/dashify.ts +0 -0
- /package/s/{features/dom → dom}/dollar.ts +0 -0
- /package/s/{features/dom → dom}/register.ts +0 -0
- /package/s/{features/dom → dom}/types.ts +0 -0
- /package/s/{features/op → ops}/loaders/make-loader.ts +0 -0
- /package/s/{features/op → ops}/loaders/parts/anims.ts +0 -0
- /package/s/{features/op → ops}/loaders/parts/ascii-anim.ts +0 -0
- /package/s/{features/op → ops}/loaders/parts/error-display.ts +0 -0
- /package/s/{features/op → ops}/types.ts +0 -0
- /package/s/{features/views → views}/css-reset.ts +0 -0
- /package/s/{features/views → views}/types.ts +0 -0
- /package/s/{features/views → views}/utils/apply-attrs.ts +0 -0
- /package/s/{features/views → views}/utils/apply-styles.ts +0 -0
- /package/s/{features/views → views}/utils/mounts.ts +0 -0
- /package/x/{features/dom → dom}/dashify.d.ts +0 -0
- /package/x/{features/dom → dom}/dashify.js +0 -0
- /package/x/{features/dom → dom}/dollar.d.ts +0 -0
- /package/x/{features/dom → dom}/dollar.js +0 -0
- /package/x/{features/dom → dom}/register.d.ts +0 -0
- /package/x/{features/dom → dom}/register.js +0 -0
- /package/x/{features/dom → dom}/types.d.ts +0 -0
- /package/x/{features/dom → dom}/types.js +0 -0
- /package/x/{features/op → ops}/loaders/make-loader.d.ts +0 -0
- /package/x/{features/op → ops}/loaders/make-loader.js +0 -0
- /package/x/{features/op → ops}/loaders/parts/anims.d.ts +0 -0
- /package/x/{features/op → ops}/loaders/parts/anims.js +0 -0
- /package/x/{features/op → ops}/loaders/parts/ascii-anim.d.ts +0 -0
- /package/x/{features/op → ops}/loaders/parts/ascii-anim.js +0 -0
- /package/x/{features/op → ops}/loaders/parts/error-display.js +0 -0
- /package/x/{features/op → ops}/types.d.ts +0 -0
- /package/x/{features/op → ops}/types.js +0 -0
- /package/x/{features/views → views}/css-reset.d.ts +0 -0
- /package/x/{features/views → views}/css-reset.js +0 -0
- /package/x/{features/views → views}/types.d.ts +0 -0
- /package/x/{features/views → views}/types.js +0 -0
- /package/x/{features/views → views}/utils/apply-attrs.d.ts +0 -0
- /package/x/{features/views → views}/utils/apply-attrs.js +0 -0
- /package/x/{features/views → views}/utils/apply-styles.d.ts +0 -0
- /package/x/{features/views → views}/utils/apply-styles.js +0 -0
- /package/x/{features/views → views}/utils/mounts.d.ts +0 -0
- /package/x/{features/views → views}/utils/mounts.js +0 -0
package/README.md
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
# 🦝 sly — mischievous shadow views
|
|
5
5
|
> testing page at https://sly.e280.org/
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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 of iteration and suffering
|
|
13
|
+
- 🌅 sly is the successor that replaces [@benev/slate](https://github.com/benevolent-games/slate)
|
|
12
14
|
- 🧑💻 project by [@e280](https://e280.org/)
|
|
13
15
|
|
|
14
16
|
<br/>
|
|
@@ -16,63 +18,74 @@
|
|
|
16
18
|
## 🦝 INSTALL SLY AND PALS
|
|
17
19
|
|
|
18
20
|
```sh
|
|
19
|
-
npm install @e280/sly @e280/
|
|
21
|
+
npm install @e280/sly lit @e280/strata @e280/stz
|
|
20
22
|
```
|
|
21
23
|
|
|
24
|
+
> [!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
|
|
29
|
+
|
|
22
30
|
<br/>
|
|
23
31
|
|
|
24
32
|
## 🦝 SLY VIEWS
|
|
25
|
-
views are the crown jewel of sly. shadow-dom'd. hooks-based. fancy ergonomics.
|
|
33
|
+
views are the crown jewel of sly. shadow-dom'd. hooks-based. fancy ergonomics.
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
```ts
|
|
36
|
+
view(use => () => "hello world")
|
|
37
|
+
```
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
views are not web components.
|
|
30
40
|
|
|
31
|
-
-
|
|
32
|
-
```ts
|
|
33
|
-
import {view} from "@e280/sly"
|
|
41
|
+
where web components are html-native, views are typescript-native — with views, there's no dom registration or string tag names, you just import them and the types work.
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
web components are best for giving html authors access to your cool widgets.. and that's cool, because any sly view can be registered as a web component.
|
|
44
|
+
|
|
45
|
+
views automatically rerender whenever any state stuff from [@e280/strata](https://github.com/e280/strata) changes.
|
|
46
|
+
|
|
47
|
+
🥷 views have a [shadow root](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).
|
|
48
|
+
views have the good parts of web components, but they aren't cumbersome.
|
|
37
49
|
|
|
38
50
|
### 🍋 view example
|
|
39
|
-
-
|
|
40
|
-
- **declaring a view**
|
|
51
|
+
- **import some stuff**
|
|
41
52
|
```ts
|
|
42
|
-
import {view} from "@e280/sly"
|
|
53
|
+
import {$, view} from "@e280/sly"
|
|
43
54
|
import {html, css} from "lit"
|
|
44
|
-
|
|
55
|
+
```
|
|
56
|
+
- **declaring a view**
|
|
57
|
+
```ts
|
|
45
58
|
export const CounterView = view(use => (start: number) => {
|
|
46
59
|
use.name("counter")
|
|
47
60
|
use.styles(css`p {color: green}`)
|
|
61
|
+
|
|
48
62
|
const count = use.signal(start)
|
|
63
|
+
const increment = () => { count.value++ }
|
|
49
64
|
|
|
50
65
|
return html`
|
|
51
66
|
<p>count ${count()}</p>
|
|
52
|
-
<button @click="${
|
|
67
|
+
<button @click="${increment}">+</button>
|
|
53
68
|
`
|
|
54
69
|
})
|
|
55
70
|
```
|
|
56
|
-
- each view renders into a `<sly-view>` host
|
|
57
|
-
- **
|
|
71
|
+
- each view renders into a `<sly-view view="counter">` host (where "counter" is the `use.name` you provided)
|
|
72
|
+
- **inject a view into the dom**
|
|
58
73
|
```ts
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
$.render($(".app"), html`
|
|
75
|
+
<h1>my cool counter demo</h1>
|
|
61
76
|
|
|
62
|
-
const content = html`
|
|
63
|
-
<h1>my demo page</h1>
|
|
64
77
|
${CounterView(1)}
|
|
65
|
-
`
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
`)
|
|
79
|
+
```
|
|
80
|
+
- 🤯 **register view as a web component**
|
|
81
|
+
```ts
|
|
82
|
+
$.register({MyCounter: CounterView.component(1)})
|
|
83
|
+
// <my-counter></my-counter>
|
|
68
84
|
```
|
|
69
85
|
|
|
70
86
|
### 🍋 view declaration settings
|
|
71
87
|
- special settings for views at declaration-time
|
|
72
88
|
```ts
|
|
73
|
-
import {view} from "@e280/sly"
|
|
74
|
-
import {html} from "lit"
|
|
75
|
-
|
|
76
89
|
export const CoolView = view
|
|
77
90
|
.settings({mode: "open", delegatesFocus: true})
|
|
78
91
|
.view(use => (greeting: string) => {
|
|
@@ -86,24 +99,42 @@ sly views are wired to automatically rerender whenever they're using any state s
|
|
|
86
99
|
### 🍋 view injection options
|
|
87
100
|
- options for views at the template injection site
|
|
88
101
|
```ts
|
|
89
|
-
|
|
90
|
-
import {CoolView} from "./cool-view.js"
|
|
91
|
-
|
|
92
|
-
const content = html`
|
|
102
|
+
$.render($(".app"), html`
|
|
93
103
|
<h2>super cool example</h2>
|
|
94
104
|
${CoolView
|
|
95
105
|
.attr("class", "hero")
|
|
96
106
|
.children(html`<em>spongebob</em>`)
|
|
97
107
|
.props("hello")}
|
|
98
|
-
`
|
|
99
|
-
|
|
100
|
-
render(content, document.querySelector(".app")!)
|
|
108
|
+
`)
|
|
101
109
|
```
|
|
102
110
|
- `attr` — set html attributes on the `<sly-view>` host element
|
|
103
111
|
- `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)
|
|
104
112
|
- `props` — finally inject the view by providing its props
|
|
105
113
|
|
|
106
|
-
### 🍋
|
|
114
|
+
### 🍋 web components
|
|
115
|
+
- **build a component directly**
|
|
116
|
+
```ts
|
|
117
|
+
const MyComponent = view.component(use => html`hello world`)
|
|
118
|
+
```
|
|
119
|
+
- notice that components don't take props
|
|
120
|
+
- **convert any view into a web component**
|
|
121
|
+
```ts
|
|
122
|
+
const MyCounter = CounterView.component(1)
|
|
123
|
+
```
|
|
124
|
+
- to convert a view to a component, you provide props
|
|
125
|
+
- note that the component instance has a render method like `element.render(2)` which can take new props
|
|
126
|
+
- **register web components to the dom**
|
|
127
|
+
```ts
|
|
128
|
+
$.register({MyComponent, MyCounter})
|
|
129
|
+
// <my-component></my-component>
|
|
130
|
+
// <my-counter></my-counter>
|
|
131
|
+
```
|
|
132
|
+
- `$.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
|
|
133
|
+
|
|
134
|
+
### 🍋 view "use" hooks reference
|
|
135
|
+
- 👮 **follow the hooks rules**
|
|
136
|
+
> just like [react hooks](https://react.dev/warnings/invalid-hook-call-warning), the execution order of sly's `use` hooks actually matters..
|
|
137
|
+
> 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..*
|
|
107
138
|
- **use.name** — set the "view" attr value, eg `<sly-view view="squarepants">`
|
|
108
139
|
```ts
|
|
109
140
|
use.name("squarepants")
|
|
@@ -117,19 +148,19 @@ sly views are wired to automatically rerender whenever they're using any state s
|
|
|
117
148
|
const count = use.signal(1)
|
|
118
149
|
|
|
119
150
|
// read the signal
|
|
120
|
-
count()
|
|
151
|
+
count() // 1
|
|
121
152
|
|
|
122
153
|
// write the signal
|
|
123
154
|
count(2)
|
|
124
155
|
```
|
|
125
|
-
- **use.once** — run fn at initialization
|
|
156
|
+
- **use.once** — run fn at initialization, and return a value
|
|
126
157
|
```ts
|
|
127
158
|
const whatever = use.once(() => {
|
|
128
159
|
console.log("happens only once")
|
|
129
160
|
return 123
|
|
130
161
|
})
|
|
131
162
|
|
|
132
|
-
whatever
|
|
163
|
+
whatever // 123
|
|
133
164
|
```
|
|
134
165
|
- **use.mount** — setup mount/unmount lifecycle
|
|
135
166
|
```ts
|
|
@@ -141,6 +172,15 @@ sly views are wired to automatically rerender whenever they're using any state s
|
|
|
141
172
|
}
|
|
142
173
|
})
|
|
143
174
|
```
|
|
175
|
+
- **use.wake** — run fn each time mounted, and return value
|
|
176
|
+
```ts
|
|
177
|
+
const whatever = use.wake(() => {
|
|
178
|
+
console.log("view mounted")
|
|
179
|
+
return 123
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
whatever // 123
|
|
183
|
+
```
|
|
144
184
|
- **use.life** — mount/unmount lifecycle, but also return a value
|
|
145
185
|
```ts
|
|
146
186
|
const v = use.life(() => {
|
|
@@ -149,22 +189,47 @@ sly views are wired to automatically rerender whenever they're using any state s
|
|
|
149
189
|
return [value, () => console.log("unmounted")]
|
|
150
190
|
})
|
|
151
191
|
|
|
152
|
-
v
|
|
192
|
+
v // 123
|
|
153
193
|
```
|
|
154
|
-
- **use.
|
|
194
|
+
- **use.attrs** — ergonomic typed html attribute access
|
|
195
|
+
```ts
|
|
196
|
+
const attrs = use.attrs({
|
|
197
|
+
name: String,
|
|
198
|
+
count: Number,
|
|
199
|
+
active: Boolean,
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
```ts
|
|
203
|
+
attrs.name // "chase"
|
|
204
|
+
attrs.count // 123
|
|
205
|
+
attrs.active // true
|
|
206
|
+
```
|
|
207
|
+
```ts
|
|
208
|
+
attrs.name = "zenky"
|
|
209
|
+
attrs.count = 124
|
|
210
|
+
attrs.active = false // removes html attr
|
|
211
|
+
```
|
|
212
|
+
```ts
|
|
213
|
+
attrs.name = undefined // removes the attr
|
|
214
|
+
```
|
|
215
|
+
- **use.render** — rerender the view (debounced)
|
|
155
216
|
```ts
|
|
156
217
|
use.render()
|
|
157
218
|
```
|
|
219
|
+
- **use.renderNow** — rerender the view instantly (not debounced)
|
|
220
|
+
```ts
|
|
221
|
+
use.renderNow()
|
|
222
|
+
```
|
|
158
223
|
- **use.rendered** — promise that resolves *after* the next render
|
|
159
224
|
```ts
|
|
160
225
|
use.rendered.then(() => {
|
|
161
|
-
const slot = use.shadow.querySelector("slot")
|
|
226
|
+
const slot = use.shadow.querySelector("slot")
|
|
162
227
|
console.log(slot)
|
|
163
228
|
})
|
|
164
229
|
```
|
|
165
|
-
- **use.op
|
|
230
|
+
- **use.op** — start with an op based on an async fn
|
|
166
231
|
```ts
|
|
167
|
-
const op = use.op
|
|
232
|
+
const op = use.op(async() => {
|
|
168
233
|
await nap(5000)
|
|
169
234
|
return 123
|
|
170
235
|
})
|
|
@@ -174,30 +239,7 @@ sly views are wired to automatically rerender whenever they're using any state s
|
|
|
174
239
|
const op = use.op.promise(doAsyncWork())
|
|
175
240
|
```
|
|
176
241
|
|
|
177
|
-
### 🍋
|
|
178
|
-
- convert any view into a proper web component
|
|
179
|
-
```ts
|
|
180
|
-
CounterView.component(1)
|
|
181
|
-
```
|
|
182
|
-
- or build a component directly
|
|
183
|
-
```ts
|
|
184
|
-
const MyComponent = view(use => html`hello world`)
|
|
185
|
-
```
|
|
186
|
-
- register web components to the dom like this
|
|
187
|
-
```ts
|
|
188
|
-
import {$} from "@e280/sly"
|
|
189
|
-
|
|
190
|
-
$.register({
|
|
191
|
-
MyCounter: CounterView.component(1),
|
|
192
|
-
MyComponent,
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
// <my-counter></my-counter>
|
|
196
|
-
// <my-component></my-component>
|
|
197
|
-
```
|
|
198
|
-
- `$.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
|
|
199
|
-
|
|
200
|
-
### 🍋 neat tricks to impress the ladies
|
|
242
|
+
### 🍋 view "use" recipes
|
|
201
243
|
- make a ticker — mount, repeat, and nap
|
|
202
244
|
```ts
|
|
203
245
|
import {repeat, nap} from "@e280/stz"
|
|
@@ -210,65 +252,182 @@ sly views are wired to automatically rerender whenever they're using any state s
|
|
|
210
252
|
seconds.value++
|
|
211
253
|
}))
|
|
212
254
|
```
|
|
213
|
-
-
|
|
255
|
+
- wake + rendered, to do something after each mount's first render
|
|
214
256
|
```ts
|
|
215
|
-
use.
|
|
257
|
+
use.wake(() => use.rendered.then(() => {
|
|
216
258
|
console.log("after first render")
|
|
217
259
|
}))
|
|
218
260
|
```
|
|
219
261
|
|
|
220
262
|
<br/>
|
|
221
263
|
|
|
222
|
-
## 🦝 SLY
|
|
223
|
-
|
|
264
|
+
## 🦝 SLY `$` DOM MULTITOOL
|
|
265
|
+
|
|
266
|
+
### 💲 follow the money
|
|
267
|
+
- import the dollarsign
|
|
224
268
|
```ts
|
|
225
269
|
import {$} from "@e280/sly"
|
|
226
270
|
```
|
|
227
|
-
|
|
271
|
+
|
|
272
|
+
### 💲 dom queries
|
|
273
|
+
- require an element
|
|
228
274
|
```ts
|
|
229
275
|
$(".demo")
|
|
230
|
-
// HTMLElement
|
|
276
|
+
// HTMLElement (or throws error)
|
|
231
277
|
```
|
|
232
|
-
|
|
233
|
-
### queries
|
|
234
|
-
- query an element (undefined if not found)
|
|
278
|
+
- request an element
|
|
235
279
|
```ts
|
|
236
280
|
$.maybe(".demo")
|
|
237
281
|
// HTMLElement | undefined
|
|
238
282
|
```
|
|
239
|
-
- query all elements
|
|
283
|
+
- query all elements
|
|
240
284
|
```ts
|
|
241
|
-
$.
|
|
242
|
-
|
|
285
|
+
for (const item of $.all("ul li"))
|
|
286
|
+
console.log(item)
|
|
243
287
|
```
|
|
244
|
-
-
|
|
288
|
+
- specify what element to query under
|
|
245
289
|
```ts
|
|
246
|
-
|
|
247
|
-
// HTMLElement
|
|
290
|
+
$("li", listElement)
|
|
291
|
+
// HTMLElement
|
|
248
292
|
```
|
|
249
293
|
|
|
250
|
-
### dom
|
|
294
|
+
### 💲 dom utilities
|
|
295
|
+
- render content into an element
|
|
296
|
+
```ts
|
|
297
|
+
$.render(element, html`hello world`)
|
|
298
|
+
```
|
|
251
299
|
- register web components
|
|
252
300
|
```ts
|
|
253
301
|
$.register({MyComponent, AnotherCoolComponent})
|
|
254
302
|
// <my-component>
|
|
255
303
|
// <another-cool-component>
|
|
256
304
|
```
|
|
257
|
-
- render content into an element
|
|
258
|
-
```ts
|
|
259
|
-
$.render(element, html`hello world`)
|
|
260
|
-
```
|
|
261
305
|
|
|
262
306
|
<br/>
|
|
263
307
|
|
|
264
308
|
## 🦝 SLY OPS, PODS, AND LOADERS
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
309
|
+
async operations and displaying loading spinners.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
import {nap} from "@e280/stz"
|
|
313
|
+
import {Pod, podium, Op, makeLoader, anims} from "@e280/sly"
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### 🫛 pods: loading/ready/error
|
|
317
|
+
- a pod represents an async operation in terms of json-serializable data
|
|
318
|
+
- there are three kinds of `Pod<V>`
|
|
319
|
+
```ts
|
|
320
|
+
// loading pod
|
|
321
|
+
["loading"]
|
|
322
|
+
|
|
323
|
+
// ready pod contains value 123
|
|
324
|
+
["ready", 123]
|
|
325
|
+
|
|
326
|
+
// error pod contains an error
|
|
327
|
+
["error", new Error()]
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 🫛 podium: helps you work with pods
|
|
331
|
+
- get pod status
|
|
332
|
+
```ts
|
|
333
|
+
podium.status(["ready", 123])
|
|
334
|
+
// "ready"
|
|
335
|
+
```
|
|
336
|
+
- get pod ready value (or undefined)
|
|
337
|
+
```ts
|
|
338
|
+
podium.value(["loading"])
|
|
339
|
+
// undefined
|
|
340
|
+
|
|
341
|
+
podium.value(["ready", 123])
|
|
342
|
+
// 123
|
|
343
|
+
```
|
|
344
|
+
- see more at [podium.ts](./s/ops/podium.ts)
|
|
345
|
+
|
|
346
|
+
### 🫛 ops: nice pod ergonomics
|
|
347
|
+
- an `Op<V>` wraps a pod with a signal for reactivity
|
|
348
|
+
- create an op
|
|
349
|
+
```ts
|
|
350
|
+
const op = new Op<number>() // loading status by default
|
|
351
|
+
```
|
|
352
|
+
```ts
|
|
353
|
+
const op = Op.loading<number>()
|
|
354
|
+
```
|
|
355
|
+
```ts
|
|
356
|
+
const op = Op.ready<number>(123)
|
|
357
|
+
```
|
|
358
|
+
```ts
|
|
359
|
+
const op = Op.error<number>(new Error())
|
|
360
|
+
```
|
|
361
|
+
- 🔥 create an op that calls and tracks an async fn
|
|
362
|
+
```ts
|
|
363
|
+
const op = Op.fn(async() => {
|
|
364
|
+
await nap(4000)
|
|
365
|
+
return 123
|
|
366
|
+
})
|
|
367
|
+
```
|
|
368
|
+
- await for the next ready value (or thrown error)
|
|
369
|
+
```ts
|
|
370
|
+
await op // 123
|
|
371
|
+
```
|
|
372
|
+
- get pod info
|
|
373
|
+
```ts
|
|
374
|
+
op.pod // ["loading"]
|
|
375
|
+
op.status // "loading"
|
|
376
|
+
op.value // undefined (or value if ready)
|
|
377
|
+
```
|
|
378
|
+
```ts
|
|
379
|
+
op.isLoading // true
|
|
380
|
+
op.isReady // false
|
|
381
|
+
op.isError // false
|
|
382
|
+
```
|
|
383
|
+
- select executes a fn based on the status
|
|
384
|
+
```ts
|
|
385
|
+
const result = op.select({
|
|
386
|
+
loading: () => "it's loading...",
|
|
387
|
+
ready: value => `dude, it's ready! ${value}`,
|
|
388
|
+
error: err => `dude, there's an error!`,
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
result
|
|
392
|
+
// "dude, it's ready! 123"
|
|
393
|
+
```
|
|
394
|
+
- morph returns a new pod, transforming the value if ready
|
|
395
|
+
```ts
|
|
396
|
+
op.morph(n => n + 1)
|
|
397
|
+
// ["ready", 124]
|
|
398
|
+
```
|
|
399
|
+
- you can combine a number of ops into a single pod like this
|
|
400
|
+
```ts
|
|
401
|
+
Op.all(Op.ready(123), Op.loading())
|
|
402
|
+
// ["loading"]
|
|
403
|
+
```
|
|
404
|
+
```ts
|
|
405
|
+
Op.all(Op.ready(1), Op.ready(2), Op.ready(3))
|
|
406
|
+
// ["ready", [1, 2, 3]]
|
|
407
|
+
```
|
|
408
|
+
- error if any ops are in error, otherwise
|
|
409
|
+
- loading if any ops are in loading, otherwise
|
|
410
|
+
- ready if all the ops are ready
|
|
411
|
+
|
|
412
|
+
### 🫛 loaders: animated loading spinners
|
|
413
|
+
- create a `loader` using `makeLoader`
|
|
414
|
+
```ts
|
|
415
|
+
const loader = makeLoader(anims.dots)
|
|
416
|
+
```
|
|
417
|
+
- see all the anims available on the testing page https://sly.e280.org/
|
|
418
|
+
- use the loader to render your op
|
|
419
|
+
```ts
|
|
420
|
+
return html`
|
|
421
|
+
<h2>cool stuff</h2>
|
|
422
|
+
|
|
423
|
+
${loader(op, value => html`
|
|
424
|
+
<div>${value}</div>
|
|
425
|
+
`)}
|
|
426
|
+
`
|
|
427
|
+
```
|
|
428
|
+
- when the op is loading, the loading spinner will animate
|
|
429
|
+
- when the op is in error, the error will be displayed
|
|
430
|
+
- when the op is ready, your fn is called and given the value
|
|
272
431
|
|
|
273
432
|
<br/>
|
|
274
433
|
|
package/package.json
CHANGED
package/s/demo/demo.bundle.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
|
|
2
|
+
import {$} from "../dom/dollar.js"
|
|
2
3
|
import {DemoView} from "./views/demo.js"
|
|
3
|
-
import {$} from "../features/dom/dollar.js"
|
|
4
4
|
import {CounterView} from "./views/counter.js"
|
|
5
5
|
|
|
6
6
|
$.render($(".demo"), DemoView())
|
|
7
|
-
|
|
8
|
-
$.register({
|
|
9
|
-
DemoCounter: CounterView.component(1),
|
|
10
|
-
})
|
|
7
|
+
$.register({DemoCounter: CounterView.component(1)})
|
|
11
8
|
|
|
12
9
|
console.log("🦝 sly")
|
|
13
10
|
|
package/s/demo/views/counter.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import {css, html} from "lit"
|
|
3
3
|
import {repeat} from "@e280/stz"
|
|
4
4
|
|
|
5
|
-
import {view} from "../../
|
|
6
|
-
import {cssReset} from "../../
|
|
5
|
+
import {view} from "../../views/view.js"
|
|
6
|
+
import {cssReset} from "../../views/css-reset.js"
|
|
7
7
|
|
|
8
8
|
export const CounterView = view(use => (initial: number) => {
|
|
9
9
|
use.name("counter")
|
package/s/demo/views/demo.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
import {css, html} from "lit"
|
|
3
|
+
import {view} from "../../views/view.js"
|
|
3
4
|
import {CounterView} from "./counter.js"
|
|
4
5
|
import {LoadersView} from "./loaders.js"
|
|
5
|
-
import {
|
|
6
|
-
import {cssReset} from "../../features/views/css-reset.js"
|
|
6
|
+
import {cssReset} from "../../views/css-reset.js"
|
|
7
7
|
|
|
8
8
|
export const DemoView = view(use => () => {
|
|
9
9
|
use.name("demo")
|
package/s/demo/views/loaders.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
import {css, html} from "lit"
|
|
3
|
-
import {Op} from "../../
|
|
4
|
-
import {view} from "../../
|
|
5
|
-
import {cssReset} from "../../
|
|
6
|
-
import {anims, makeLoader} from "../../
|
|
3
|
+
import {Op} from "../../ops/op.js"
|
|
4
|
+
import {view} from "../../views/view.js"
|
|
5
|
+
import {cssReset} from "../../views/css-reset.js"
|
|
6
|
+
import {anims, makeLoader} from "../../ops/loaders/make-loader.js"
|
|
7
7
|
|
|
8
8
|
export const LoadersView = view(use => () => {
|
|
9
9
|
use.name("loaders")
|
package/s/index.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
2
|
+
export * from "./dom/dashify.js"
|
|
3
|
+
export * from "./dom/dollar.js"
|
|
4
|
+
export * from "./dom/register.js"
|
|
5
|
+
export * from "./dom/types.js"
|
|
6
6
|
|
|
7
|
-
export * from "./
|
|
8
|
-
export * from "./
|
|
9
|
-
export * from "./
|
|
7
|
+
export * from "./ops/loaders/make-loader.js"
|
|
8
|
+
export * from "./ops/loaders/parts/ascii-anim.js"
|
|
9
|
+
export * from "./ops/loaders/parts/error-display.js"
|
|
10
10
|
|
|
11
|
-
export * from "./
|
|
12
|
-
export * from "./
|
|
13
|
-
export * from "./
|
|
11
|
+
export * from "./ops/op.js"
|
|
12
|
+
export * from "./ops/podium.js"
|
|
13
|
+
export * from "./ops/types.js"
|
|
14
14
|
|
|
15
|
-
export * from "./
|
|
16
|
-
export * from "./
|
|
17
|
-
export * from "./
|
|
18
|
-
export * from "./
|
|
15
|
+
export * from "./views/attributes.js"
|
|
16
|
+
export * from "./views/css-reset.js"
|
|
17
|
+
export * from "./views/types.js"
|
|
18
|
+
export * from "./views/use.js"
|
|
19
|
+
export * from "./views/view.js"
|
|
19
20
|
|
|
@@ -22,11 +22,11 @@ export class Op<V> {
|
|
|
22
22
|
|
|
23
23
|
static all<V>(...ops: Op<V>[]) {
|
|
24
24
|
const pods = ops.map(op => op.pod)
|
|
25
|
-
|
|
26
|
-
return new this(pod)
|
|
25
|
+
return podium.all(...pods)
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
readonly signal: Signal<Pod<V>>
|
|
29
|
+
#count = 0
|
|
30
30
|
#resolve = pub<[V]>()
|
|
31
31
|
#reject = pub<[any]>()
|
|
32
32
|
|
|
@@ -35,12 +35,16 @@ export class Op<V> {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
get wait() {
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
this.#resolve.next().then(resolve)
|
|
40
|
-
this.#reject.next().then(reject)
|
|
38
|
+
return new Promise<V>((resolve, reject) => {
|
|
39
|
+
this.#resolve.next().then(([v]) => resolve(v))
|
|
40
|
+
this.#reject.next().then(([e]) => reject(e))
|
|
41
41
|
})
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
get then() { return this.wait.then.bind(this.wait) }
|
|
45
|
+
get catch() { return this.wait.catch.bind(this.wait) }
|
|
46
|
+
get finally() { return this.wait.finally.bind(this.wait) }
|
|
47
|
+
|
|
44
48
|
async setLoading() {
|
|
45
49
|
await this.signal(["loading"])
|
|
46
50
|
}
|
|
@@ -56,14 +60,17 @@ export class Op<V> {
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
async promise(promise: Promise<V>) {
|
|
63
|
+
const count = ++this.#count
|
|
59
64
|
await this.setLoading()
|
|
60
65
|
try {
|
|
61
66
|
const value = await promise
|
|
62
|
-
|
|
67
|
+
if (count === this.#count)
|
|
68
|
+
await this.setReady(value)
|
|
63
69
|
return value
|
|
64
70
|
}
|
|
65
71
|
catch (error) {
|
|
66
|
-
|
|
72
|
+
if (count === this.#count)
|
|
73
|
+
await this.setError(error)
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
|
|
@@ -114,7 +121,7 @@ export class Op<V> {
|
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
morph<V2>(fn: (value: V) => V2) {
|
|
117
|
-
return
|
|
124
|
+
return podium.morph(this.pod, fn)
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
|