@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.
Files changed (114) hide show
  1. package/README.md +257 -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/{features/op → ops}/podium.ts +18 -18
  10. package/s/views/attributes.ts +89 -0
  11. package/s/{features/views → views}/use.ts +21 -5
  12. package/s/{features/views → views}/view.ts +15 -10
  13. package/x/demo/demo.bundle.js +2 -4
  14. package/x/demo/demo.bundle.js.map +1 -1
  15. package/x/demo/demo.bundle.min.js +12 -12
  16. package/x/demo/demo.bundle.min.js.map +4 -4
  17. package/x/demo/views/counter.js +2 -2
  18. package/x/demo/views/counter.js.map +1 -1
  19. package/x/demo/views/demo.js +2 -2
  20. package/x/demo/views/demo.js.map +1 -1
  21. package/x/demo/views/loaders.js +4 -4
  22. package/x/demo/views/loaders.js.map +1 -1
  23. package/x/dom/dashify.js.map +1 -0
  24. package/x/dom/dollar.js.map +1 -0
  25. package/x/dom/register.js.map +1 -0
  26. package/x/{features/op → dom}/types.js.map +1 -1
  27. package/x/index.d.ts +15 -14
  28. package/x/index.html +2 -2
  29. package/x/index.js +15 -14
  30. package/x/index.js.map +1 -1
  31. package/x/ops/loaders/make-loader.js.map +1 -0
  32. package/x/ops/loaders/parts/anims.js.map +1 -0
  33. package/x/ops/loaders/parts/ascii-anim.js.map +1 -0
  34. package/x/ops/loaders/parts/error-display.d.ts +1 -0
  35. package/x/ops/loaders/parts/error-display.js.map +1 -0
  36. package/x/{features/op → ops}/op.d.ts +6 -3
  37. package/x/{features/op → ops}/op.js +13 -7
  38. package/x/ops/op.js.map +1 -0
  39. package/x/ops/podium.d.ts +9 -0
  40. package/x/{features/op → ops}/podium.js +18 -18
  41. package/x/ops/podium.js.map +1 -0
  42. package/x/{features/dom → ops}/types.js.map +1 -1
  43. package/x/views/attributes.d.ts +10 -0
  44. package/x/views/attributes.js +46 -0
  45. package/x/views/attributes.js.map +1 -0
  46. package/x/views/css-reset.js.map +1 -0
  47. package/x/{features/views → views}/types.js.map +1 -1
  48. package/x/{features/views → views}/use.d.ts +9 -4
  49. package/x/{features/views → views}/use.js +21 -6
  50. package/x/views/use.js.map +1 -0
  51. package/x/views/utils/apply-attrs.js.map +1 -0
  52. package/x/views/utils/apply-styles.js.map +1 -0
  53. package/x/views/utils/mounts.js.map +1 -0
  54. package/x/{features/views → views}/view.d.ts +2 -3
  55. package/x/{features/views → views}/view.js +7 -7
  56. package/x/views/view.js.map +1 -0
  57. package/x/features/dom/dashify.js.map +0 -1
  58. package/x/features/dom/dollar.js.map +0 -1
  59. package/x/features/dom/register.js.map +0 -1
  60. package/x/features/op/loaders/make-loader.js.map +0 -1
  61. package/x/features/op/loaders/parts/anims.js.map +0 -1
  62. package/x/features/op/loaders/parts/ascii-anim.js.map +0 -1
  63. package/x/features/op/loaders/parts/error-display.d.ts +0 -1
  64. package/x/features/op/loaders/parts/error-display.js.map +0 -1
  65. package/x/features/op/op.js.map +0 -1
  66. package/x/features/op/podium.d.ts +0 -9
  67. package/x/features/op/podium.js.map +0 -1
  68. package/x/features/views/css-reset.js.map +0 -1
  69. package/x/features/views/use.js.map +0 -1
  70. package/x/features/views/utils/apply-attrs.js.map +0 -1
  71. package/x/features/views/utils/apply-styles.js.map +0 -1
  72. package/x/features/views/utils/mounts.js.map +0 -1
  73. package/x/features/views/view.js.map +0 -1
  74. /package/s/{features/dom → dom}/dashify.ts +0 -0
  75. /package/s/{features/dom → dom}/dollar.ts +0 -0
  76. /package/s/{features/dom → dom}/register.ts +0 -0
  77. /package/s/{features/dom → dom}/types.ts +0 -0
  78. /package/s/{features/op → ops}/loaders/make-loader.ts +0 -0
  79. /package/s/{features/op → ops}/loaders/parts/anims.ts +0 -0
  80. /package/s/{features/op → ops}/loaders/parts/ascii-anim.ts +0 -0
  81. /package/s/{features/op → ops}/loaders/parts/error-display.ts +0 -0
  82. /package/s/{features/op → ops}/types.ts +0 -0
  83. /package/s/{features/views → views}/css-reset.ts +0 -0
  84. /package/s/{features/views → views}/types.ts +0 -0
  85. /package/s/{features/views → views}/utils/apply-attrs.ts +0 -0
  86. /package/s/{features/views → views}/utils/apply-styles.ts +0 -0
  87. /package/s/{features/views → views}/utils/mounts.ts +0 -0
  88. /package/x/{features/dom → dom}/dashify.d.ts +0 -0
  89. /package/x/{features/dom → dom}/dashify.js +0 -0
  90. /package/x/{features/dom → dom}/dollar.d.ts +0 -0
  91. /package/x/{features/dom → dom}/dollar.js +0 -0
  92. /package/x/{features/dom → dom}/register.d.ts +0 -0
  93. /package/x/{features/dom → dom}/register.js +0 -0
  94. /package/x/{features/dom → dom}/types.d.ts +0 -0
  95. /package/x/{features/dom → dom}/types.js +0 -0
  96. /package/x/{features/op → ops}/loaders/make-loader.d.ts +0 -0
  97. /package/x/{features/op → ops}/loaders/make-loader.js +0 -0
  98. /package/x/{features/op → ops}/loaders/parts/anims.d.ts +0 -0
  99. /package/x/{features/op → ops}/loaders/parts/anims.js +0 -0
  100. /package/x/{features/op → ops}/loaders/parts/ascii-anim.d.ts +0 -0
  101. /package/x/{features/op → ops}/loaders/parts/ascii-anim.js +0 -0
  102. /package/x/{features/op → ops}/loaders/parts/error-display.js +0 -0
  103. /package/x/{features/op → ops}/types.d.ts +0 -0
  104. /package/x/{features/op → ops}/types.js +0 -0
  105. /package/x/{features/views → views}/css-reset.d.ts +0 -0
  106. /package/x/{features/views → views}/css-reset.js +0 -0
  107. /package/x/{features/views → views}/types.d.ts +0 -0
  108. /package/x/{features/views → views}/types.js +0 -0
  109. /package/x/{features/views → views}/utils/apply-attrs.d.ts +0 -0
  110. /package/x/{features/views → views}/utils/apply-attrs.js +0 -0
  111. /package/x/{features/views → views}/utils/apply-styles.d.ts +0 -0
  112. /package/x/{features/views → views}/utils/apply-styles.js +0 -0
  113. /package/x/{features/views → views}/utils/mounts.d.ts +0 -0
  114. /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,74 @@
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
+ > - 🏂 [@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. 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).
48
+ views have the good parts of web components, but they aren't cumbersome.
37
49
 
38
50
  ### 🍋 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**
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="${() => { count.value++ }}">+</button>
67
+ <button @click="${increment}">+</button>
53
68
  `
54
69
  })
55
70
  ```
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**
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
- import {render, html} from "lit"
60
- import {CounterView} from "./my-counter.js"
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
- render(content, document.querySelector(".app")!)
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
- import {render, html} from "lit"
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
- ### 🍋 view `use` reference
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() //-> 1
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 //-> 123
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 //-> 123
192
+ v // 123
153
193
  ```
154
- - **use.render** — force a hard render (not debounced)
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.fn** — start with an op based on an async fn
230
+ - **use.op** — start with an op based on an async fn
166
231
  ```ts
167
- const op = use.op.fn(async() => {
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
- ### 🍋 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
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
- - once+rendered to do an action after the first render
255
+ - wake + rendered, to do something after each mount's first render
214
256
  ```ts
215
- use.once(() => use.rendered.then(() => {
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'S `$` DOLLAR DOM MULTITOOL
223
- - import the `$` and it has a bunch of goodies
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
- - query an element (throws an error if not found)
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 (returns an array)
283
+ - query all elements
240
284
  ```ts
241
- $.maybe("ul li")
242
- // HTMLElement[]
285
+ for (const item of $.all("ul li"))
286
+ console.log(item)
243
287
  ```
244
- - query all elements (returns an array)
288
+ - specify what element to query under
245
289
  ```ts
246
- $.maybe("ul li")
247
- // HTMLElement[]
290
+ $("li", listElement)
291
+ // HTMLElement
248
292
  ```
249
293
 
250
- ### dom stuff
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
- > ***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
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e280/sly",
3
- "version": "0.0.0-7",
3
+ "version": "0.0.0-9",
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