@e280/sly 0.0.0-1 → 0.0.0-2
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 +183 -3
- package/package.json +3 -5
- package/s/demo/demo.bundle.ts +49 -0
- package/s/demo/demo.css +6 -0
- package/s/features/dom/dashify.ts +11 -0
- package/s/features/dom/dollar.ts +21 -0
- package/s/features/dom/register.ts +48 -0
- package/s/features/dom/types.ts +5 -0
- package/s/features/loady/ascii-loader.ts +38 -0
- package/s/features/loady/parts/ascii-anim.ts +27 -0
- package/s/features/loady/parts/ascii-loader.ts +14 -0
- package/s/features/loady/parts/error-display.ts +26 -0
- package/s/features/op/op.ts +98 -0
- package/s/features/op/pod.ts +19 -0
- package/s/features/op/types.ts +12 -0
- package/s/features/views/css-reset.ts +21 -0
- package/s/features/views/types.ts +23 -0
- package/s/features/views/use.ts +75 -0
- package/s/features/views/utils/apply-attrs.ts +33 -0
- package/s/features/views/utils/apply-styles.ts +21 -0
- package/s/features/views/utils/mounts.ts +22 -0
- package/s/features/views/view.ts +98 -0
- package/s/index.html.ts +2 -1
- package/s/index.ts +18 -0
- package/x/demo/demo.bundle.d.ts +1 -0
- package/x/demo/demo.bundle.js +37 -1
- package/x/demo/demo.bundle.js.map +1 -1
- package/x/demo/demo.bundle.min.js +72 -1
- package/x/demo/demo.bundle.min.js.map +4 -4
- package/x/demo/demo.css +6 -0
- package/x/features/dom/dashify.d.ts +7 -0
- package/x/features/dom/dashify.js +10 -0
- package/x/features/dom/dashify.js.map +1 -0
- package/x/features/dom/dollar.d.ts +8 -0
- package/x/features/dom/dollar.js +15 -0
- package/x/features/dom/dollar.js.map +1 -0
- package/x/features/dom/register.d.ts +18 -0
- package/x/features/dom/register.js +29 -0
- package/x/features/dom/register.js.map +1 -0
- package/x/features/dom/types.d.ts +5 -0
- package/x/features/dom/types.js +2 -0
- package/x/features/dom/types.js.map +1 -0
- package/x/features/loady/ascii-loader.d.ts +5 -0
- package/x/features/loady/ascii-loader.js +33 -0
- package/x/features/loady/ascii-loader.js.map +1 -0
- package/x/features/loady/parts/ascii-anim.d.ts +8 -0
- package/x/features/loady/parts/ascii-anim.js +21 -0
- package/x/features/loady/parts/ascii-anim.js.map +1 -0
- package/x/features/loady/parts/ascii-loader.d.ts +3 -0
- package/x/features/loady/parts/ascii-loader.js +10 -0
- package/x/features/loady/parts/ascii-loader.js.map +1 -0
- package/x/features/loady/parts/error-display.d.ts +8 -0
- package/x/features/loady/parts/error-display.js +20 -0
- package/x/features/loady/parts/error-display.js.map +1 -0
- package/x/features/op/op.d.ts +21 -0
- package/x/features/op/op.js +79 -0
- package/x/features/op/op.js.map +1 -0
- package/x/features/op/pod.d.ts +5 -0
- package/x/features/op/pod.js +16 -0
- package/x/features/op/pod.js.map +1 -0
- package/x/features/op/types.d.ts +9 -0
- package/x/features/op/types.js +2 -0
- package/x/features/op/types.js.map +1 -0
- package/x/features/views/css-reset.d.ts +2 -0
- package/x/features/views/css-reset.js +19 -0
- package/x/features/views/css-reset.js.map +1 -0
- package/x/features/views/types.d.ts +16 -0
- package/x/features/views/types.js +2 -0
- package/x/features/views/types.js.map +1 -0
- package/x/features/views/use.d.ts +25 -0
- package/x/features/views/use.js +62 -0
- package/x/features/views/use.js.map +1 -0
- package/x/features/views/utils/apply-attrs.d.ts +2 -0
- package/x/features/views/utils/apply-attrs.js +21 -0
- package/x/features/views/utils/apply-attrs.js.map +1 -0
- package/x/features/views/utils/apply-styles.d.ts +2 -0
- package/x/features/views/utils/apply-styles.js +16 -0
- package/x/features/views/utils/apply-styles.js.map +1 -0
- package/x/features/views/utils/mounts.d.ts +6 -0
- package/x/features/views/utils/mounts.js +18 -0
- package/x/features/views/utils/mounts.js.map +1 -0
- package/x/features/views/view.d.ts +16 -0
- package/x/features/views/view.js +81 -0
- package/x/features/views/view.js.map +1 -0
- package/x/index.d.ts +13 -0
- package/x/index.html +10 -3
- package/x/index.html.js +2 -1
- package/x/index.html.js.map +1 -1
- package/x/index.js +13 -1
- package/x/index.js.map +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,187 @@
|
|
|
1
1
|
|
|
2
|
-
<div align="center"><img alt="" width="
|
|
2
|
+
<div align="center"><img alt="" width="256" src="./assets/favicon.png"/></div>
|
|
3
3
|
|
|
4
|
-
# 🦝 sly
|
|
4
|
+
# 🦝 sly — mischievious frontend web framework
|
|
5
|
+
- 🪒 lean view framework for [lit](https://lit.dev/)
|
|
6
|
+
- 🌅 sly is the successor to [@benev/slate](https://github.com/benevolent-games/slate)
|
|
7
|
+
- 🏂 commonly used with stz standard library [@e280/stz](https://github.com/e280/stz)
|
|
8
|
+
- ⛏️ integrates signals and state trees from [@e280/strata](https://github.com/e280/strata)
|
|
9
|
+
- 🐢 if you need a buildy-bundler-buddy, try [scute](https://github.com/e280/scute)
|
|
10
|
+
- 🧑💻 project by [@e280](https://e280.org/)
|
|
5
11
|
|
|
6
|
-
|
|
12
|
+
<br/>
|
|
13
|
+
|
|
14
|
+
## 📦 INSTALL SLY AND PEERS
|
|
15
|
+
they all super work together.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install @e280/sly @e280/stz @e280/strata lit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
<br/>
|
|
22
|
+
|
|
23
|
+
## 🪟 VIEWS ARE LEAN
|
|
24
|
+
views are the crown jewel of sly. shadow-dom'd. hooks-based. fancy ergonomics. not components.
|
|
25
|
+
|
|
26
|
+
views are leaner than web components.. no dom registration, 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.
|
|
27
|
+
|
|
28
|
+
sly views are wired to automatically rerender whenever they're using any state stuff from [@e280/strata](https://github.com/e280/strata).
|
|
29
|
+
|
|
30
|
+
### 🍋 basic view example
|
|
31
|
+
> views are hooks-based functional components. they are *not* web components nor *custom elements*, yet they are encapsulated within a [shadow root](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM).
|
|
32
|
+
|
|
33
|
+
#### declaring a view
|
|
34
|
+
```ts
|
|
35
|
+
import {view} from "@e280/sly"
|
|
36
|
+
import {html, css} from "lit"
|
|
37
|
+
|
|
38
|
+
export const CounterView = view(use => (start: number) => {
|
|
39
|
+
use.name("counter")
|
|
40
|
+
use.styles(css`p {color: green}`)
|
|
41
|
+
const count = use.signal(start)
|
|
42
|
+
|
|
43
|
+
return html`
|
|
44
|
+
<p>count ${count()}</p>
|
|
45
|
+
<button @click="${() => { count.value++ }}"></button>
|
|
46
|
+
`
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
- each view renders into a `<sly-view>` host, with the provided `name` set as its view attribute, eg `<sly-view view="counter">`
|
|
50
|
+
|
|
51
|
+
#### injecting a view into the dom
|
|
52
|
+
```ts
|
|
53
|
+
import {render, html} from "lit"
|
|
54
|
+
import {CounterView} from "./my-counter.js"
|
|
55
|
+
|
|
56
|
+
const content = html`
|
|
57
|
+
<h1>my demo page</h1>
|
|
58
|
+
${CounterView(1)}
|
|
59
|
+
`
|
|
60
|
+
|
|
61
|
+
render(content, document.body)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 🍋 view `use`
|
|
65
|
+
> super special view helper, with hooks and other goodies
|
|
66
|
+
|
|
67
|
+
- **use.signal** — create a [strata signal](https://github.com/e280/strata)
|
|
68
|
+
```ts
|
|
69
|
+
const count = use.signal(1)
|
|
70
|
+
|
|
71
|
+
// read the signal
|
|
72
|
+
count() //-> 1
|
|
73
|
+
|
|
74
|
+
// write the signal
|
|
75
|
+
count(2)
|
|
76
|
+
```
|
|
77
|
+
- **use.once** — run fn at initialization
|
|
78
|
+
```ts
|
|
79
|
+
const whatever = use.once(() => {
|
|
80
|
+
console.log("happens only once")
|
|
81
|
+
return 123
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
whatever //-> 123
|
|
85
|
+
```
|
|
86
|
+
- **use.mount** — setup mount/unmount lifecycle
|
|
87
|
+
```ts
|
|
88
|
+
use.mount(() => {
|
|
89
|
+
console.log("view mounted")
|
|
90
|
+
|
|
91
|
+
return () => {
|
|
92
|
+
console.log("view unmounted")
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
- **use.name** — set the "view" attr value, eg `<sly-view view="squarepants">`
|
|
97
|
+
```ts
|
|
98
|
+
use.name("squarepants")
|
|
99
|
+
```
|
|
100
|
+
- **use.styles** — attach stylesheets into the view's shadow dom
|
|
101
|
+
```ts
|
|
102
|
+
use.styles(css1, css2, css3)
|
|
103
|
+
```
|
|
104
|
+
- **use.rendered** — promise that resolves *after* view has rendered
|
|
105
|
+
```ts
|
|
106
|
+
use.rendered.then(() => {
|
|
107
|
+
const slot = use.shadow.querySelector("slot")!
|
|
108
|
+
console.log(slot)
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 🍋 neat tricks to impress the ladies
|
|
113
|
+
> common patterns and snippets
|
|
114
|
+
|
|
115
|
+
- make a ticker — mount, repeat, and nap
|
|
116
|
+
```ts
|
|
117
|
+
import {repeat, nap} from "@e280/stz"
|
|
118
|
+
```
|
|
119
|
+
```ts
|
|
120
|
+
const seconds = use.signal(0)
|
|
121
|
+
|
|
122
|
+
use.mount(() => repeat(async() => {
|
|
123
|
+
await nap(1000)
|
|
124
|
+
seconds.value++
|
|
125
|
+
}))
|
|
126
|
+
```
|
|
127
|
+
- once+rendered to do an action after the first render
|
|
128
|
+
```ts
|
|
129
|
+
use.once(() => use.rendered.then(() => {
|
|
130
|
+
console.log("after first render")
|
|
131
|
+
}))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 🍋 view declaration settings
|
|
135
|
+
> special settings for views at declaration-time
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import {view} from "@e280/sly"
|
|
139
|
+
import {html} from "lit"
|
|
140
|
+
|
|
141
|
+
export const CoolView = view
|
|
142
|
+
.settings({mode: "open", delegatesFocus: true})
|
|
143
|
+
.view(use => (greeting: string) => {
|
|
144
|
+
|
|
145
|
+
return html`😎 ${greeting} <slot></slot>`
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
- these `settings` like `mode` and `delegatesFocus` are [attachShadow params](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#parameters)
|
|
149
|
+
- note the `<slot></slot>` we'll use in the next example lol
|
|
150
|
+
|
|
151
|
+
### 🍋 view injection options
|
|
152
|
+
> options for views at the template injection site
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import {render, html} from "lit"
|
|
156
|
+
import {CoolView} from "./cool-view.js"
|
|
157
|
+
|
|
158
|
+
const content = html`
|
|
159
|
+
<h2>super cool example</h2>
|
|
160
|
+
${CoolView
|
|
161
|
+
.attr("class", "hero")
|
|
162
|
+
.children(html`<em>spongebob</em>`)
|
|
163
|
+
.props("hello")}
|
|
164
|
+
`
|
|
165
|
+
|
|
166
|
+
render(content, document.body)
|
|
167
|
+
```
|
|
168
|
+
- `attr` — set html attributes on the `<sly-view>` host element
|
|
169
|
+
- `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)
|
|
170
|
+
- `props` — finally inject the view by providing its props
|
|
171
|
+
|
|
172
|
+
<br/>
|
|
173
|
+
|
|
174
|
+
## 🛠️ OPS LOADING INDICATORS
|
|
175
|
+
> ***TODO*** *implemented but not yet documented, lol*
|
|
176
|
+
- `Pod` is a type for (loading/ready/error states)
|
|
177
|
+
- `Op` class wraps a pod signal and has some ergonomic fns
|
|
178
|
+
- `loady` has various loading indicators for dealing with ops
|
|
179
|
+
|
|
180
|
+
<br/>
|
|
181
|
+
|
|
182
|
+
<br/>
|
|
183
|
+
|
|
184
|
+
## 💖 PROJECT BY e280
|
|
185
|
+
reward us with github stars
|
|
186
|
+
build with us at https://e280.org/ but only if you're cool
|
|
7
187
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e280/sly",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-2",
|
|
4
4
|
"description": "web shadow views",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,10 +13,8 @@
|
|
|
13
13
|
"s"
|
|
14
14
|
],
|
|
15
15
|
"peerDependencies": {
|
|
16
|
-
"@e280/
|
|
17
|
-
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"@e280/stz": "^0.0.0-34",
|
|
16
|
+
"@e280/stz": "^0.0.0-35",
|
|
17
|
+
"@e280/strata": "^0.0.0-10",
|
|
20
18
|
"lit": "^3.3.1"
|
|
21
19
|
},
|
|
22
20
|
"devDependencies": {
|
package/s/demo/demo.bundle.ts
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
1
|
|
|
2
|
+
import {nap, repeat} from "@e280/stz"
|
|
3
|
+
import {css, html, render} from "lit"
|
|
4
|
+
|
|
5
|
+
import {$} from "../features/dom/dollar.js"
|
|
6
|
+
import {view} from "../features/views/view.js"
|
|
7
|
+
import {cssReset} from "../features/views/css-reset.js"
|
|
8
|
+
import {loady} from "../features/loady/ascii-loader.js"
|
|
9
|
+
|
|
2
10
|
console.log("🦝 sly")
|
|
3
11
|
|
|
12
|
+
const styles = css`
|
|
13
|
+
:host {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
text-align: center;
|
|
18
|
+
}
|
|
19
|
+
`
|
|
20
|
+
|
|
21
|
+
const MyView = view(use => (greeting: string) => {
|
|
22
|
+
use.name("my-view")
|
|
23
|
+
use.styles(cssReset, styles)
|
|
24
|
+
const count = use.signal(0)
|
|
25
|
+
|
|
26
|
+
use.mount(() => repeat(async() => {
|
|
27
|
+
await nap(1000)
|
|
28
|
+
count.value++
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
use.once(() => use.rendered.then(() => {
|
|
32
|
+
console.log("slot", $("slot", use.shadow))
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
const op = use.op.fn(async() => {
|
|
36
|
+
await nap(5000)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return html`
|
|
40
|
+
<p>${greeting} <slot></slot> ${count()}</p>
|
|
41
|
+
<p>${loady.dots(op, () => "op loaded")}</p>
|
|
42
|
+
`
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
render(
|
|
46
|
+
MyView
|
|
47
|
+
.attr("class", "incredi")
|
|
48
|
+
.children("world")
|
|
49
|
+
.props("hello"),
|
|
50
|
+
$(".demo"),
|
|
51
|
+
)
|
|
52
|
+
|
package/s/demo/demo.css
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
export const $ = one
|
|
3
|
+
|
|
4
|
+
export type Queryable = HTMLElement | Document | ShadowRoot | Element
|
|
5
|
+
|
|
6
|
+
function all<E extends HTMLElement = HTMLElement>(selector: string, context: Queryable = document) {
|
|
7
|
+
return Array.from(context.querySelectorAll<E>(selector))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function one<E extends HTMLElement = HTMLElement>(selector: string, context: Queryable = document) {
|
|
11
|
+
const e = context.querySelector<E>(selector)
|
|
12
|
+
if (!e) throw new Error(`$1 ${selector} not found`)
|
|
13
|
+
return e
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
one.maybe = <E extends HTMLElement = HTMLElement>(selector: string, context: Queryable = document) => {
|
|
17
|
+
return context.querySelector<E>(selector)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
one.all = all
|
|
21
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
import {dashify} from "./dashify.js"
|
|
3
|
+
import {HTMLElementClasses} from "./types.js"
|
|
4
|
+
|
|
5
|
+
export type RegistrationOptions = {
|
|
6
|
+
soft: boolean
|
|
7
|
+
upgrade: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* register custom elements (web components) to the dom
|
|
12
|
+
* - takes an object full of custom html elements, and automatically dashes the names
|
|
13
|
+
* - eg, `MyCoolElement` is registered as `<my-cool-element></my-cool-element>`
|
|
14
|
+
* - calls `customElements.define`
|
|
15
|
+
* - option `soft`
|
|
16
|
+
* - `false` (default) will throw errors if elements are already defined
|
|
17
|
+
* - `true` will do nothing if an element is already defined
|
|
18
|
+
* - option `upgrade`
|
|
19
|
+
* - `true` (default) will run `customElements.upgrade` where appropriate
|
|
20
|
+
* - `false` will NOT upgrade any existing elements on the page
|
|
21
|
+
*/
|
|
22
|
+
export function register<E extends HTMLElementClasses>(
|
|
23
|
+
elements: E,
|
|
24
|
+
options: Partial<RegistrationOptions> = {},
|
|
25
|
+
) {
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
soft = false,
|
|
29
|
+
upgrade = true,
|
|
30
|
+
} = options
|
|
31
|
+
|
|
32
|
+
for (const [name, Element] of Object.entries(elements)) {
|
|
33
|
+
const tag = dashify(name)
|
|
34
|
+
const already = customElements.get(tag)
|
|
35
|
+
|
|
36
|
+
if (soft && already)
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
customElements.define(tag, Element)
|
|
40
|
+
|
|
41
|
+
if (upgrade)
|
|
42
|
+
document.querySelectorAll(tag).forEach(element => {
|
|
43
|
+
if (element.constructor === HTMLElement)
|
|
44
|
+
customElements.upgrade(element)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
import {makeAsciiLoader} from "./parts/ascii-loader.js";
|
|
3
|
+
|
|
4
|
+
const hz = 15
|
|
5
|
+
|
|
6
|
+
export const loady = {
|
|
7
|
+
cylon: makeAsciiLoader(hz, [
|
|
8
|
+
"=----",
|
|
9
|
+
"-=---",
|
|
10
|
+
"--=--",
|
|
11
|
+
"---=-",
|
|
12
|
+
"----=",
|
|
13
|
+
"-----",
|
|
14
|
+
"-----",
|
|
15
|
+
]),
|
|
16
|
+
|
|
17
|
+
dots: makeAsciiLoader(hz, [
|
|
18
|
+
":....",
|
|
19
|
+
"::...",
|
|
20
|
+
".::..",
|
|
21
|
+
"..::.",
|
|
22
|
+
"...::",
|
|
23
|
+
"....:",
|
|
24
|
+
".....",
|
|
25
|
+
".....",
|
|
26
|
+
]),
|
|
27
|
+
|
|
28
|
+
binary: makeAsciiLoader(hz, [
|
|
29
|
+
"01111",
|
|
30
|
+
"10111",
|
|
31
|
+
"11011",
|
|
32
|
+
"11101",
|
|
33
|
+
"11110",
|
|
34
|
+
"11111",
|
|
35
|
+
"11111",
|
|
36
|
+
]),
|
|
37
|
+
}
|
|
38
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import {css} from "lit"
|
|
3
|
+
import {nap, repeat} from "@e280/stz"
|
|
4
|
+
|
|
5
|
+
import {view} from "../../views/view.js"
|
|
6
|
+
import {cssReset} from "../../views/css-reset.js"
|
|
7
|
+
|
|
8
|
+
const style = css`
|
|
9
|
+
:host {
|
|
10
|
+
font-family: monospace;
|
|
11
|
+
}
|
|
12
|
+
`
|
|
13
|
+
|
|
14
|
+
export const AsciiAnim = view(use => (hz: number, anim: string[]) => {
|
|
15
|
+
use.name("loading")
|
|
16
|
+
use.styles(cssReset, style)
|
|
17
|
+
const frame = use.signal(0)
|
|
18
|
+
|
|
19
|
+
use.mount(() => repeat(async() => {
|
|
20
|
+
await nap(1000 / hz)
|
|
21
|
+
const next = frame() + 1
|
|
22
|
+
frame(next >= anim.length ? 0 : next)
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
return anim.at(frame())
|
|
26
|
+
})
|
|
27
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
import {Op} from "../../op/op.js"
|
|
3
|
+
import {AsciiAnim} from "./ascii-anim.js"
|
|
4
|
+
import {Content} from "../../views/types.js"
|
|
5
|
+
import {ErrorDisplay} from "./error-display.js"
|
|
6
|
+
|
|
7
|
+
export function makeAsciiLoader(hz: number, anim: string[]) {
|
|
8
|
+
return <V>(op: Op<V>, fn: (value: V) => Content) => op.select({
|
|
9
|
+
loading: () => AsciiAnim(hz, anim),
|
|
10
|
+
error: error => ErrorDisplay(error),
|
|
11
|
+
ready: fn,
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
import {css, html} from "lit"
|
|
3
|
+
import {view} from "../../views/view.js"
|
|
4
|
+
import {cssReset} from "../../views/css-reset.js"
|
|
5
|
+
|
|
6
|
+
const style = css`
|
|
7
|
+
:host {
|
|
8
|
+
font-family: monospace;
|
|
9
|
+
color: red;
|
|
10
|
+
}
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
export const ErrorDisplay = view(use => (error: any) => {
|
|
14
|
+
use.name("loading")
|
|
15
|
+
use.styles(cssReset, style)
|
|
16
|
+
|
|
17
|
+
if (typeof error === "string")
|
|
18
|
+
return error
|
|
19
|
+
|
|
20
|
+
else if (error instanceof Error)
|
|
21
|
+
return html`<strong>${error.name}:</strong> <span>${error.message}</span>`
|
|
22
|
+
|
|
23
|
+
else
|
|
24
|
+
return `error`
|
|
25
|
+
})
|
|
26
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
|
|
2
|
+
import {pub} from "@e280/stz"
|
|
3
|
+
import {signal} from "@e280/strata/signals"
|
|
4
|
+
|
|
5
|
+
import {pod} from "./pod.js"
|
|
6
|
+
import {Pod, PodSelect} from "./types.js"
|
|
7
|
+
|
|
8
|
+
export class Op<V> {
|
|
9
|
+
static loading() {
|
|
10
|
+
return new this()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static ready<V>(value: V) {
|
|
14
|
+
const op = new this<V>()
|
|
15
|
+
op.signal(["ready", value])
|
|
16
|
+
return op
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static error(error: any) {
|
|
20
|
+
const op = new this()
|
|
21
|
+
op.signal(["error", error])
|
|
22
|
+
return op
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static promise<V>(promise: Promise<V>) {
|
|
26
|
+
const op = new this<V>()
|
|
27
|
+
op.promise(promise)
|
|
28
|
+
return op
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static fn<V>(fn: () => Promise<V>) {
|
|
32
|
+
return this.promise(fn())
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
readonly signal = signal<Pod<V>>(["loading"])
|
|
36
|
+
#resolve = pub<[V]>()
|
|
37
|
+
#reject = pub<[any]>()
|
|
38
|
+
|
|
39
|
+
get wait() {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
this.#resolve.next().then(resolve)
|
|
42
|
+
this.#reject.next().then(reject)
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async setLoading() {
|
|
47
|
+
await this.signal(["loading"])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async setReady(value: V) {
|
|
51
|
+
await this.signal(["ready", value])
|
|
52
|
+
await this.#resolve(value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async setError(error: any) {
|
|
56
|
+
await this.signal(["error", error])
|
|
57
|
+
await this.#reject(error)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async promise(promise: Promise<V>) {
|
|
61
|
+
await this.setLoading()
|
|
62
|
+
try {
|
|
63
|
+
const value = await promise
|
|
64
|
+
await this.setReady(value)
|
|
65
|
+
return value
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
await this.setError(error)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async fn(fn: () => Promise<V>) {
|
|
73
|
+
return this.promise(fn())
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get pod() {
|
|
77
|
+
return this.signal()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get status() {
|
|
81
|
+
return this.signal()[0]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get value() {
|
|
85
|
+
return pod.value(this.signal())
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
require() {
|
|
89
|
+
const pod = this.signal()
|
|
90
|
+
if (pod[0] !== "ready") throw new Error("required value not ready")
|
|
91
|
+
return pod[1]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
select<R>(select: PodSelect<V, R>) {
|
|
95
|
+
return pod.select(this.signal(), select)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
import {Pod, PodSelect} from "./types.js"
|
|
3
|
+
|
|
4
|
+
export const pod = {
|
|
5
|
+
value: <V>(pod: Pod<V>) => {
|
|
6
|
+
return pod[0] === "ready"
|
|
7
|
+
? pod[1]
|
|
8
|
+
: undefined
|
|
9
|
+
},
|
|
10
|
+
select: <V, R>(pod: Pod<V>, select: PodSelect<V, R>) => {
|
|
11
|
+
switch (pod[0]) {
|
|
12
|
+
case "loading": return select.loading()
|
|
13
|
+
case "error": return select.error(pod[1])
|
|
14
|
+
case "ready": return select.ready(pod[1])
|
|
15
|
+
default: throw new Error("unknown op status")
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
export type PodLoading = [status: "loading"]
|
|
3
|
+
export type PodReady<V> = [status: "ready", value: V]
|
|
4
|
+
export type PodError = [status: "error", error: any]
|
|
5
|
+
export type Pod<V> = PodLoading | PodReady<V> | PodError
|
|
6
|
+
|
|
7
|
+
export type PodSelect<V, R> = {
|
|
8
|
+
loading: () => R
|
|
9
|
+
ready: (value: V) => R
|
|
10
|
+
error: (error: any) => R
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
import {css, CSSResultGroup} from "lit"
|
|
3
|
+
|
|
4
|
+
export const cssReset: CSSResultGroup = css`
|
|
5
|
+
@layer reset {
|
|
6
|
+
* {
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 0;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
|
|
11
|
+
scrollbar-width: thin;
|
|
12
|
+
scrollbar-color: #888 transparent;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
::-webkit-scrollbar { width: 8px; }
|
|
16
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
17
|
+
::-webkit-scrollbar-thumb { background: #333; border-radius: 1em; }
|
|
18
|
+
::-webkit-scrollbar-thumb:hover { background: #444; }
|
|
19
|
+
}
|
|
20
|
+
`
|
|
21
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
import {DirectiveResult} from "lit/directive.js"
|
|
3
|
+
import {CSSResultGroup, TemplateResult} from "lit"
|
|
4
|
+
|
|
5
|
+
import {Use} from "./use.js"
|
|
6
|
+
|
|
7
|
+
export type Content = TemplateResult | DirectiveResult | HTMLElement | string | null | undefined | void
|
|
8
|
+
export type AttrValue = string | boolean | number | undefined | null | void
|
|
9
|
+
|
|
10
|
+
export type ViewFn<Props extends any[]> = (use: Use) => (...props: Props) => Content
|
|
11
|
+
export type View<Props extends any[]> = (...props: Props) => DirectiveResult<any>
|
|
12
|
+
|
|
13
|
+
export type ViewSettings = ShadowRootInit & {
|
|
14
|
+
tag?: string
|
|
15
|
+
name?: string
|
|
16
|
+
styles?: CSSResultGroup
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ViewWith = {
|
|
20
|
+
children: Content
|
|
21
|
+
attrs: Record<string, AttrValue>
|
|
22
|
+
}
|
|
23
|
+
|