@e280/sly 0.0.0-7 → 0.0.0-8

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