@e280/sly 0.2.0-7 → 0.2.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 +87 -24
- package/package.json +1 -1
- package/s/demo/demo.bundle.ts +13 -1
- package/s/demo/views/counter.ts +13 -5
- package/s/demo/views/demo.ts +10 -0
- package/s/demo/views/divine.ts +22 -0
- package/s/demo/views/incredi.ts +2 -1
- package/s/dom/attrs/attrs.ts +21 -0
- package/s/dom/attrs/parts/attr-fns.ts +38 -0
- package/s/dom/attrs/parts/attr-proxies.ts +35 -0
- package/s/dom/attrs/parts/attr-spec.ts +29 -0
- package/s/dom/attrs/parts/on-attrs.ts +8 -0
- package/s/dom/dom.ts +6 -5
- package/s/dom/{register.ts → parts/register.ts} +2 -10
- package/s/dom/types.ts +45 -0
- package/s/index.html.ts +2 -1
- package/s/index.ts +1 -4
- package/s/views/base-element.ts +15 -31
- package/s/views/types.ts +15 -6
- package/s/views/use.ts +10 -11
- package/s/views/utils/attr-watcher.ts +22 -0
- package/s/views/utils/reactor.ts +23 -0
- package/s/views/view.ts +73 -38
- package/x/demo/demo.bundle.js +11 -1
- package/x/demo/demo.bundle.js.map +1 -1
- package/x/demo/demo.bundle.min.js +19 -15
- package/x/demo/demo.bundle.min.js.map +4 -4
- package/x/demo/views/counter.d.ts +7 -1
- package/x/demo/views/counter.js +11 -5
- package/x/demo/views/counter.js.map +1 -1
- package/x/demo/views/demo.js +8 -0
- package/x/demo/views/demo.js.map +1 -1
- package/x/demo/views/divine.d.ts +8 -0
- package/x/demo/views/divine.js +19 -0
- package/x/demo/views/divine.js.map +1 -0
- package/x/demo/views/incredi.js +1 -1
- package/x/demo/views/incredi.js.map +1 -1
- package/x/dom/attrs/attrs.d.ts +20 -0
- package/x/dom/attrs/attrs.js +17 -0
- package/x/dom/attrs/attrs.js.map +1 -0
- package/x/dom/attrs/parts/attr-fns.d.ts +13 -0
- package/x/dom/attrs/parts/attr-fns.js +42 -0
- package/x/dom/attrs/parts/attr-fns.js.map +1 -0
- package/x/dom/attrs/parts/attr-proxies.d.ts +8 -0
- package/x/dom/attrs/parts/attr-proxies.js +21 -0
- package/x/dom/attrs/parts/attr-proxies.js.map +1 -0
- package/x/dom/attrs/parts/attr-spec.d.ts +3 -0
- package/x/dom/attrs/parts/attr-spec.js +21 -0
- package/x/dom/attrs/parts/attr-spec.js.map +1 -0
- package/x/dom/attrs/parts/on-attrs.d.ts +2 -0
- package/x/dom/attrs/parts/on-attrs.js +7 -0
- package/x/dom/attrs/parts/on-attrs.js.map +1 -0
- package/x/dom/dom.d.ts +10 -4
- package/x/dom/dom.js +5 -5
- package/x/dom/dom.js.map +1 -1
- package/x/dom/parts/dashify.js.map +1 -0
- package/x/dom/{register.d.ts → parts/register.d.ts} +2 -10
- package/x/dom/parts/register.js.map +1 -0
- package/x/dom/{attributes.d.ts → types.d.ts} +11 -2
- package/x/dom/types.js +2 -0
- package/x/dom/types.js.map +1 -0
- package/x/index.d.ts +1 -4
- package/x/index.html +4 -3
- package/x/index.html.js +2 -1
- package/x/index.html.js.map +1 -1
- package/x/index.js +1 -4
- package/x/index.js.map +1 -1
- package/x/views/base-element.js +9 -27
- package/x/views/base-element.js.map +1 -1
- package/x/views/types.d.ts +11 -6
- package/x/views/use.d.ts +2 -2
- package/x/views/use.js +2 -5
- package/x/views/use.js.map +1 -1
- package/x/views/utils/attr-watcher.d.ts +8 -0
- package/x/views/utils/attr-watcher.js +20 -0
- package/x/views/utils/attr-watcher.js.map +1 -0
- package/x/views/utils/reactor.d.ts +6 -0
- package/x/views/utils/reactor.js +18 -0
- package/x/views/utils/reactor.js.map +1 -0
- package/x/views/view.d.ts +8 -4
- package/x/views/view.js +59 -30
- package/x/views/view.js.map +1 -1
- package/s/dom/attributes.ts +0 -89
- package/x/dom/attributes.js +0 -46
- package/x/dom/attributes.js.map +0 -1
- package/x/dom/dashify.js.map +0 -1
- package/x/dom/register.js.map +0 -1
- /package/s/dom/{dashify.ts → parts/dashify.ts} +0 -0
- /package/x/dom/{dashify.d.ts → parts/dashify.d.ts} +0 -0
- /package/x/dom/{dashify.js → parts/dashify.js} +0 -0
- /package/x/dom/{register.js → parts/register.js} +0 -0
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
## 🦝 sly and friends
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
npm install @e280/sly lit
|
|
23
|
+
npm install @e280/sly lit @e280/strata @e280/stz
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
> [!NOTE]
|
|
@@ -34,15 +34,15 @@ npm install @e280/sly lit
|
|
|
34
34
|
<br/><br/>
|
|
35
35
|
<a id="views"></a>
|
|
36
36
|
|
|
37
|
-
## 🦝🍋 sly views
|
|
37
|
+
## 🦝🍋 sly views and components
|
|
38
38
|
> *views are the crown jewel of sly.. shadow-dom'd.. hooks-based.. "ergonomics"..*
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
41
|
view(use => () => html`<p>hello world</p>`)
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
- any view can be converted into a web component
|
|
44
45
|
- views are not [web components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components), but they do have [shadow roots](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) and support [slots](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots)
|
|
45
|
-
- any view can be registered as a web component, perfect for entrypoints or sharing widgets with html authors
|
|
46
46
|
- views are typescript-native and comfy for webdevs building apps
|
|
47
47
|
- views automatically rerender whenever any [strata-compatible](https://github.com/e280/strata) state changes
|
|
48
48
|
|
|
@@ -76,8 +76,14 @@ import {html, css} from "lit"
|
|
|
76
76
|
```
|
|
77
77
|
- 🤯 **register a view as a web component**
|
|
78
78
|
```ts
|
|
79
|
-
dom.register({
|
|
80
|
-
|
|
79
|
+
dom.register({
|
|
80
|
+
MyCounter: CounterView
|
|
81
|
+
.component()
|
|
82
|
+
.props(component => [dom.attrs(component).number.start ?? 0]),
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
```html
|
|
86
|
+
<my-counter start="1"></my-counter>
|
|
81
87
|
```
|
|
82
88
|
|
|
83
89
|
### 🍋 view declaration settings
|
|
@@ -85,7 +91,7 @@ import {html, css} from "lit"
|
|
|
85
91
|
```ts
|
|
86
92
|
export const CoolView = view
|
|
87
93
|
.settings({mode: "open", delegatesFocus: true})
|
|
88
|
-
.
|
|
94
|
+
.render(use => (greeting: string) => {
|
|
89
95
|
return html`😎 ${greeting} <slot></slot>`
|
|
90
96
|
})
|
|
91
97
|
```
|
|
@@ -108,27 +114,77 @@ import {html, css} from "lit"
|
|
|
108
114
|
- `children` — nested content in the host element, can be [slotted](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_templates_and_slots)
|
|
109
115
|
- `render` — end the view chain and render the lit directive
|
|
110
116
|
|
|
111
|
-
### 🍋 view
|
|
112
|
-
- **
|
|
117
|
+
### 🍋 view/component universality
|
|
118
|
+
- **start with a view,**
|
|
113
119
|
```ts
|
|
114
|
-
const
|
|
120
|
+
export const GreeterView = view(use => (name: string) => {
|
|
121
|
+
return html`<p>hello ${name}</p>`
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// view usage:
|
|
125
|
+
// GreeterView("pimsley")
|
|
115
126
|
```
|
|
116
|
-
|
|
117
|
-
- **convert any view into a web component**
|
|
127
|
+
then you can convert it to a component
|
|
118
128
|
```ts
|
|
119
|
-
|
|
129
|
+
export class GreeterComponent extends (GreeterView
|
|
130
|
+
.component()
|
|
131
|
+
.props(component => [component.getAttribute("name") ?? "unknown"])
|
|
132
|
+
) {}
|
|
133
|
+
|
|
134
|
+
// html usage:
|
|
135
|
+
// <greeter-component name="pimsley"></greeter-component>
|
|
120
136
|
```
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
- **start with a component,**
|
|
138
|
+
```ts
|
|
139
|
+
export class GreeterComponent extends (view
|
|
140
|
+
.component()
|
|
141
|
+
.props(component => [component.getAttribute("name") ?? "unknown"])
|
|
142
|
+
.render(use => (name: string) => {
|
|
143
|
+
return html`<p>hello ${name}</p>`
|
|
144
|
+
})
|
|
145
|
+
) {}
|
|
146
|
+
|
|
147
|
+
// html usage:
|
|
148
|
+
// <greeter-component name="pimsley"></greeter-component>
|
|
149
|
+
```
|
|
150
|
+
then you can already use it as a view
|
|
151
|
+
```ts
|
|
152
|
+
// view usage:
|
|
153
|
+
// GreeterComponent.view("pimsley")
|
|
154
|
+
```
|
|
155
|
+
- **understanding `.component(init)` and `.props(fn)`**
|
|
156
|
+
- `.props` takes a fn that is called every render, which returns the props given to the view
|
|
157
|
+
```ts
|
|
158
|
+
.component()
|
|
159
|
+
.props(() => ["pimsley"])
|
|
160
|
+
```
|
|
161
|
+
the props fn receives the component instance, so you can query html attributes
|
|
162
|
+
```ts
|
|
163
|
+
.component()
|
|
164
|
+
.props(component => [component.getAttribute("name") ?? "unknown"])
|
|
165
|
+
```
|
|
166
|
+
- `.component` takes a mixin type added to the component type, so your `.props` can accept instance properties
|
|
167
|
+
```ts
|
|
168
|
+
.component<{name?: string}>()
|
|
169
|
+
.props(component => [component.name ?? "unknown"])
|
|
170
|
+
```
|
|
171
|
+
- `.component` also takes an init fn, so you can do some setup, like use signals for reactivity
|
|
172
|
+
```ts
|
|
173
|
+
.component<{$name: SignalFn<string>}>(component => {
|
|
174
|
+
component.$name = signal("pimsley")
|
|
175
|
+
})
|
|
176
|
+
.props(component => [component.$name])
|
|
177
|
+
```
|
|
178
|
+
- `.component` lets you set instance properties, that devs can interact with via the dom
|
|
179
|
+
```ts
|
|
180
|
+
dom<GreeterComponent>("my-component").$name.value = "mortimer"
|
|
181
|
+
```
|
|
123
182
|
- **register web components to the dom**
|
|
124
183
|
```ts
|
|
125
|
-
dom.register({
|
|
126
|
-
// <my-component></my-component>
|
|
127
|
-
// <my-counter></my-counter>
|
|
184
|
+
dom.register({GreeterComponent})
|
|
128
185
|
```
|
|
129
|
-
- `dom.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
|
|
130
186
|
|
|
131
|
-
### 🍋
|
|
187
|
+
### 🍋 "use" hooks reference
|
|
132
188
|
- 👮 **follow the hooks rules**
|
|
133
189
|
> just like [react hooks](https://react.dev/warnings/invalid-hook-call-warning), the execution order of sly's `use` hooks actually matters..
|
|
134
190
|
> you must not call these hooks under `if` conditionals, or `for` loops, or in callbacks, or after a conditional `return` statement, or anything like that.. *otherwise, heed my warning: weird bad stuff will happen..*
|
|
@@ -201,7 +257,7 @@ import {html, css} from "lit"
|
|
|
201
257
|
- **use.attrs** — ergonomic typed html attribute access
|
|
202
258
|
*(see [dom.attrs](#dom.attrs) for more details)*
|
|
203
259
|
```ts
|
|
204
|
-
const attrs = use.attrs({
|
|
260
|
+
const attrs = use.attrs.spec({
|
|
205
261
|
name: String,
|
|
206
262
|
count: Number,
|
|
207
263
|
active: Boolean,
|
|
@@ -239,7 +295,7 @@ import {html, css} from "lit"
|
|
|
239
295
|
const op = use.op.promise(doAsyncWork())
|
|
240
296
|
```
|
|
241
297
|
|
|
242
|
-
### 🍋
|
|
298
|
+
### 🍋 "use" recipes
|
|
243
299
|
- make a ticker — mount, repeat, and nap
|
|
244
300
|
```ts
|
|
245
301
|
import {repeat, nap} from "@e280/stz"
|
|
@@ -288,7 +344,7 @@ base element enjoys the same `use` hooks as views.
|
|
|
288
344
|
start = 10
|
|
289
345
|
|
|
290
346
|
// custom attributes
|
|
291
|
-
attrs = dom.attrs(this
|
|
347
|
+
attrs = dom.attrs(this).spec({
|
|
292
348
|
multiply: Number,
|
|
293
349
|
})
|
|
294
350
|
|
|
@@ -388,6 +444,7 @@ import {dom} from "@e280/sly"
|
|
|
388
444
|
// <my-component>
|
|
389
445
|
// <another-cool-component>
|
|
390
446
|
```
|
|
447
|
+
- `dom.register` automatically dashes the tag names (`MyComponent` becomes `<my-component>`)
|
|
391
448
|
- `render` content into an element
|
|
392
449
|
```ts
|
|
393
450
|
dom(element).render(html`<p>hello world</p>`)
|
|
@@ -400,7 +457,7 @@ import {dom} from "@e280/sly"
|
|
|
400
457
|
```
|
|
401
458
|
- `attrs` <a id="dom.attrs"></a> to setup a type-happy html attribute helper
|
|
402
459
|
```ts
|
|
403
|
-
const attrs = dom.attrs(element
|
|
460
|
+
const attrs = dom.attrs(element).spec({
|
|
404
461
|
name: String,
|
|
405
462
|
count: Number,
|
|
406
463
|
active: Boolean,
|
|
@@ -420,6 +477,12 @@ import {dom} from "@e280/sly"
|
|
|
420
477
|
attrs.name = undefined // removes the attr
|
|
421
478
|
attrs.count = undefined // removes the attr
|
|
422
479
|
```
|
|
480
|
+
or if you wanna be more loosey-goosy, skip the spec
|
|
481
|
+
```ts
|
|
482
|
+
dom.attrs(element).string.name = "pimsley"
|
|
483
|
+
dom.attrs(element).number.count = 125
|
|
484
|
+
dom.attrs(element).boolean.active = true
|
|
485
|
+
```
|
|
423
486
|
|
|
424
487
|
|
|
425
488
|
|
|
@@ -599,7 +662,7 @@ import {ev} from "@e280/stz"
|
|
|
599
662
|
})
|
|
600
663
|
|
|
601
664
|
// sly attribute handler for the body
|
|
602
|
-
const attrs = dom.attrs(document.body
|
|
665
|
+
const attrs = dom.attrs(document.body).spec({
|
|
603
666
|
"data-indicator": Boolean,
|
|
604
667
|
})
|
|
605
668
|
|
package/package.json
CHANGED
package/s/demo/demo.bundle.ts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
|
|
2
|
+
import {nap, repeat} from "@e280/stz"
|
|
2
3
|
import {dom} from "../dom/dom.js"
|
|
3
4
|
import {DemoView} from "./views/demo.js"
|
|
4
5
|
import {CounterView} from "./views/counter.js"
|
|
6
|
+
import {DivineElement} from "./views/divine.js"
|
|
5
7
|
import {IncrediElement} from "./views/incredi.js"
|
|
6
8
|
|
|
7
9
|
dom.in(".demo").render(DemoView())
|
|
8
10
|
|
|
9
11
|
dom.register({
|
|
10
12
|
IncrediElement,
|
|
11
|
-
|
|
13
|
+
DivineElement,
|
|
14
|
+
DemoCounter: CounterView
|
|
15
|
+
.component()
|
|
16
|
+
.props(c => [dom.attrs(c).number.initial ?? 0]),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const divine = dom<DivineElement>("divine-element")
|
|
20
|
+
|
|
21
|
+
repeat(async() => {
|
|
22
|
+
await nap(1000)
|
|
23
|
+
divine.$speed.value++
|
|
12
24
|
})
|
|
13
25
|
|
|
14
26
|
console.log("🦝 sly")
|
package/s/demo/views/counter.ts
CHANGED
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
import {css, html} from "lit"
|
|
3
3
|
import {repeat} from "@e280/stz"
|
|
4
4
|
|
|
5
|
+
import {dom} from "../../dom/dom.js"
|
|
5
6
|
import {view} from "../../views/view.js"
|
|
6
7
|
import {cssReset} from "../../views/css-reset.js"
|
|
7
8
|
|
|
8
|
-
export const CounterView = view(use => (
|
|
9
|
+
export const CounterView = view(use => (start: number) => {
|
|
9
10
|
use.name("counter")
|
|
10
11
|
use.styles(cssReset, styles)
|
|
11
12
|
|
|
12
13
|
const $seconds = use.signal(0)
|
|
13
|
-
const
|
|
14
|
+
const since = use.once(() => Date.now())
|
|
14
15
|
use.mount(() => repeat(async() => {
|
|
15
|
-
const
|
|
16
|
-
$seconds.set(Math.floor(
|
|
16
|
+
const delta = Date.now() - since
|
|
17
|
+
$seconds.set(Math.floor(delta / 1000))
|
|
17
18
|
}))
|
|
18
19
|
|
|
19
|
-
const $count = use.signal(
|
|
20
|
+
const $count = use.signal(start)
|
|
20
21
|
const increment = () => $count.value++
|
|
21
22
|
|
|
22
23
|
const $product = use.signal
|
|
@@ -39,6 +40,13 @@ export const CounterView = view(use => (initial: number) => {
|
|
|
39
40
|
`
|
|
40
41
|
})
|
|
41
42
|
|
|
43
|
+
// convert a view into a web component
|
|
44
|
+
export class CounterComponent extends (
|
|
45
|
+
CounterView
|
|
46
|
+
.component<{start?: number}>()
|
|
47
|
+
.props(c => [dom.attrs(c).number.start ?? 0])
|
|
48
|
+
) {}
|
|
49
|
+
|
|
42
50
|
const styles = css`
|
|
43
51
|
:host {
|
|
44
52
|
display: flex;
|
package/s/demo/views/demo.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
|
|
2
2
|
import {css, html} from "lit"
|
|
3
|
+
import {DivineView} from "./divine.js"
|
|
3
4
|
import {view} from "../../views/view.js"
|
|
4
5
|
import {CounterView} from "./counter.js"
|
|
5
6
|
import {LoadersView} from "./loaders.js"
|
|
6
7
|
import {cssReset} from "../../views/css-reset.js"
|
|
8
|
+
import { nap, repeat } from "@e280/stz"
|
|
7
9
|
|
|
8
10
|
export const DemoView = view(use => () => {
|
|
9
11
|
use.name("demo")
|
|
10
12
|
use.styles(cssReset, styles)
|
|
11
13
|
|
|
14
|
+
const $speed = use.signal(3)
|
|
15
|
+
|
|
16
|
+
use.mount(() => repeat(async() => {
|
|
17
|
+
await nap(1000)
|
|
18
|
+
$speed.value++
|
|
19
|
+
}))
|
|
20
|
+
|
|
12
21
|
return html`
|
|
13
22
|
${CounterView.props(2).children("view").render()}
|
|
23
|
+
${DivineView($speed())}
|
|
14
24
|
${LoadersView()}
|
|
15
25
|
`
|
|
16
26
|
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
import {html} from "lit"
|
|
3
|
+
import {view} from "../../views/view.js"
|
|
4
|
+
import {signal, SignalFn} from "@e280/strata"
|
|
5
|
+
|
|
6
|
+
export class DivineElement extends (view
|
|
7
|
+
.component<{$speed: SignalFn<number>}>(component => {
|
|
8
|
+
component.$speed = signal(1)
|
|
9
|
+
})
|
|
10
|
+
.props<[speed: number]>(component => [component.$speed()])
|
|
11
|
+
.render(use => speed => {
|
|
12
|
+
const $count = use.signal(0)
|
|
13
|
+
const increment = () => $count($count() + speed)
|
|
14
|
+
return html`
|
|
15
|
+
<span>${$count()}</span>
|
|
16
|
+
<button @click="${increment}">+${speed}</button>
|
|
17
|
+
`
|
|
18
|
+
})
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
export const DivineView = DivineElement.view
|
|
22
|
+
|
package/s/demo/views/incredi.ts
CHANGED
|
@@ -8,7 +8,8 @@ import {BaseElement} from "../../views/base-element.js"
|
|
|
8
8
|
|
|
9
9
|
export class IncrediElement extends BaseElement {
|
|
10
10
|
static styles = css`span{color:orange}`
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
attrs = dom.attrs(this).spec({value: Number})
|
|
12
13
|
something = {whatever: "rofl"}
|
|
13
14
|
|
|
14
15
|
render(use: Use) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
import {AttrSpec} from "../types.js"
|
|
3
|
+
import {onAttrs} from "./parts/on-attrs.js"
|
|
4
|
+
import {attrFns} from "./parts/attr-fns.js"
|
|
5
|
+
import {attrSpec} from "./parts/attr-spec.js"
|
|
6
|
+
import {AttrProxies} from "./parts/attr-proxies.js"
|
|
7
|
+
|
|
8
|
+
export function attrs(element: HTMLElement) {
|
|
9
|
+
const proxies = new AttrProxies(element)
|
|
10
|
+
return {
|
|
11
|
+
string: proxies.string,
|
|
12
|
+
number: proxies.number,
|
|
13
|
+
boolean: proxies.boolean,
|
|
14
|
+
on: (fn: () => void) => onAttrs(element, fn),
|
|
15
|
+
spec: <A extends AttrSpec>(spec: A) => attrSpec(element, spec),
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
attrs.get = attrFns.get
|
|
20
|
+
attrs.set = attrFns.set
|
|
21
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
/** fns for getting and setting html attributes of various types */
|
|
3
|
+
export const attrFns = {
|
|
4
|
+
get: {
|
|
5
|
+
string: (e: HTMLElement, key: string) => {
|
|
6
|
+
return e.getAttribute(key) ?? undefined
|
|
7
|
+
},
|
|
8
|
+
number: (e: HTMLElement, key: string) => {
|
|
9
|
+
const raw = e.getAttribute(key)
|
|
10
|
+
return (raw === null || !raw)
|
|
11
|
+
? undefined
|
|
12
|
+
: Number(raw)
|
|
13
|
+
},
|
|
14
|
+
boolean: (e: HTMLElement, key: string) => {
|
|
15
|
+
const raw = e.getAttribute(key)
|
|
16
|
+
return raw !== null
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
set: {
|
|
21
|
+
string: (e: HTMLElement, key: string, value: string | undefined) => {
|
|
22
|
+
if (value === undefined) e.removeAttribute(key)
|
|
23
|
+
else e.setAttribute(key, value)
|
|
24
|
+
return true
|
|
25
|
+
},
|
|
26
|
+
number: (e: HTMLElement, key: string, value: number | undefined) => {
|
|
27
|
+
if (value === undefined) e.removeAttribute(key)
|
|
28
|
+
else e.setAttribute(key, value.toString())
|
|
29
|
+
return true
|
|
30
|
+
},
|
|
31
|
+
boolean: (e: HTMLElement, key: string, value: boolean | undefined) => {
|
|
32
|
+
if (value) e.setAttribute(key, "")
|
|
33
|
+
else e.removeAttribute(key)
|
|
34
|
+
return true
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
import {attrFns} from "./attr-fns.js"
|
|
3
|
+
|
|
4
|
+
/** a typed proxy accessor for html attributes */
|
|
5
|
+
export class AttrProxies {
|
|
6
|
+
constructor(public element: HTMLElement) {}
|
|
7
|
+
|
|
8
|
+
string = new Proxy({}, {
|
|
9
|
+
get: (_t, key: string) => (
|
|
10
|
+
attrFns.get.string(this.element, key)
|
|
11
|
+
),
|
|
12
|
+
set: (_t, key: string, value: string | undefined) => (
|
|
13
|
+
attrFns.set.string(this.element, key, value)
|
|
14
|
+
),
|
|
15
|
+
}) as Record<string, string | undefined>
|
|
16
|
+
|
|
17
|
+
number = new Proxy({}, {
|
|
18
|
+
get: (_t, key: string) => (
|
|
19
|
+
attrFns.get.number(this.element, key)
|
|
20
|
+
),
|
|
21
|
+
set: (_t, key: string, value: number | undefined) => (
|
|
22
|
+
attrFns.set.number(this.element, key, value)
|
|
23
|
+
),
|
|
24
|
+
}) as Record<string, number | undefined>
|
|
25
|
+
|
|
26
|
+
boolean = new Proxy({}, {
|
|
27
|
+
get: (_t, key: string) => (
|
|
28
|
+
attrFns.get.boolean(this.element, key)
|
|
29
|
+
),
|
|
30
|
+
set: (_t, key: string, value: boolean | undefined) => (
|
|
31
|
+
attrFns.set.boolean(this.element, key, value)
|
|
32
|
+
),
|
|
33
|
+
}) as Record<string, boolean | undefined>
|
|
34
|
+
}
|
|
35
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
import {AttrSpec, AttrTypes} from "../../types.js"
|
|
3
|
+
import {attrFns} from "./attr-fns.js"
|
|
4
|
+
|
|
5
|
+
/** specify available html attributes and their types and create a proxy accessor */
|
|
6
|
+
export const attrSpec = <A extends AttrSpec>(
|
|
7
|
+
e: HTMLElement,
|
|
8
|
+
spec: A,
|
|
9
|
+
) => new Proxy(spec, {
|
|
10
|
+
|
|
11
|
+
get: (_target, key: string) => {
|
|
12
|
+
switch (spec[key]) {
|
|
13
|
+
case String: return attrFns.get.string(e, key)
|
|
14
|
+
case Number: return attrFns.get.number(e, key)
|
|
15
|
+
case Boolean: return attrFns.get.boolean(e, key)
|
|
16
|
+
default: throw new Error(`invalid attribute type for "${key}"`)
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
set: (_target, key: string, value: any) => {
|
|
21
|
+
switch (spec[key]) {
|
|
22
|
+
case String: return attrFns.set.string(e, key, value)
|
|
23
|
+
case Number: return attrFns.set.number(e, key, value)
|
|
24
|
+
case Boolean: return attrFns.set.boolean(e, key, value)
|
|
25
|
+
default: throw new Error(`invalid attribute type for "${key}"`)
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
}) as any as AttrTypes<A>
|
|
29
|
+
|
package/s/dom/dom.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
import {render} from "lit"
|
|
3
|
-
import {
|
|
3
|
+
import {AttrSpec} from "./types.js"
|
|
4
|
+
import {attrs} from "./attrs/attrs.js"
|
|
4
5
|
import {Content} from "../views/types.js"
|
|
5
|
-
import {
|
|
6
|
+
import {register} from "./parts/register.js"
|
|
6
7
|
|
|
7
8
|
export type Renderable = HTMLElement | ShadowRoot | DocumentFragment
|
|
8
9
|
export type Queryable = HTMLElement | ShadowRoot | Element | Document | DocumentFragment
|
|
@@ -45,8 +46,8 @@ export class Dom<C extends Queryable> {
|
|
|
45
46
|
return render(content, this.element as Renderable)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
attrs
|
|
49
|
-
return
|
|
49
|
+
attrs() {
|
|
50
|
+
return attrs(this.element as HTMLElement)
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -64,7 +65,7 @@ dom.require = doc.require.bind(doc)
|
|
|
64
65
|
dom.maybe = doc.maybe.bind(doc)
|
|
65
66
|
dom.all = doc.all.bind(doc)
|
|
66
67
|
|
|
67
|
-
dom.attrs =
|
|
68
|
+
dom.attrs = attrs
|
|
68
69
|
dom.register = register
|
|
69
70
|
dom.render = (container: Renderable, ...content: Content[]) => {
|
|
70
71
|
return render(content, container)
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import {dashify} from "./dashify.js"
|
|
3
|
-
|
|
4
|
-
export type HTMLElementClasses = {
|
|
5
|
-
[key: string]: {new(...args: any[]): HTMLElement}
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type RegistrationOptions = {
|
|
9
|
-
soft: boolean
|
|
10
|
-
upgrade: boolean
|
|
11
|
-
}
|
|
3
|
+
import {HTMLElementClasses, RegisterOptions} from "../types.js"
|
|
12
4
|
|
|
13
5
|
/**
|
|
14
6
|
* register custom elements (web components) to the dom
|
|
@@ -24,7 +16,7 @@ export type RegistrationOptions = {
|
|
|
24
16
|
*/
|
|
25
17
|
export function register<E extends HTMLElementClasses>(
|
|
26
18
|
elements: E,
|
|
27
|
-
options: Partial<
|
|
19
|
+
options: Partial<RegisterOptions> = {},
|
|
28
20
|
) {
|
|
29
21
|
|
|
30
22
|
const {
|
package/s/dom/types.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
import {attrs} from "./attrs/attrs.js"
|
|
3
|
+
|
|
4
|
+
// attrs
|
|
5
|
+
|
|
6
|
+
export type AttrKind = (
|
|
7
|
+
| typeof String
|
|
8
|
+
| typeof Number
|
|
9
|
+
| typeof Boolean
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
export type AttrType<H extends AttrKind> = (
|
|
13
|
+
H extends typeof String
|
|
14
|
+
? string | undefined
|
|
15
|
+
|
|
16
|
+
: H extends typeof Number
|
|
17
|
+
? number | undefined
|
|
18
|
+
|
|
19
|
+
: H extends typeof Boolean
|
|
20
|
+
? boolean
|
|
21
|
+
|
|
22
|
+
: never
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
export type AttrSpec = {
|
|
26
|
+
[key: string]: AttrKind
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type AttrTypes<A extends AttrSpec> = {
|
|
30
|
+
[P in keyof A]: AttrType<A[P]>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type Attrs = ReturnType<typeof attrs>
|
|
34
|
+
|
|
35
|
+
// register
|
|
36
|
+
|
|
37
|
+
export type HTMLElementClasses = {
|
|
38
|
+
[key: string]: {new(...args: any[]): HTMLElement}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type RegisterOptions = {
|
|
42
|
+
soft: boolean
|
|
43
|
+
upgrade: boolean
|
|
44
|
+
}
|
|
45
|
+
|
package/s/index.html.ts
CHANGED
|
@@ -30,7 +30,8 @@ export default ssg.page(import.meta.url, async orb => ({
|
|
|
30
30
|
<p><a href="https://github.com/e280/sly">github.com/e280/sly</a></p>
|
|
31
31
|
<p class=lil>v${orb.packageVersion()}</p>
|
|
32
32
|
<incredi-element></incredi-element>
|
|
33
|
-
<
|
|
33
|
+
<divine-element></divine-element>
|
|
34
|
+
<demo-counter initial="-1">component</demo-counter>
|
|
34
35
|
<div class=demo></div>
|
|
35
36
|
`,
|
|
36
37
|
}))
|
package/s/index.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
export * from "./dom/
|
|
3
|
-
export * from "./dom/dashify.js"
|
|
2
|
+
export * from "./dom/types.js"
|
|
4
3
|
export * from "./dom/dom.js"
|
|
5
|
-
export * from "./dom/register.js"
|
|
6
4
|
|
|
7
5
|
export * from "./ops/loaders/make-loader.js"
|
|
8
6
|
export * from "./ops/loaders/parts/ascii-anim.js"
|
|
@@ -13,7 +11,6 @@ export * from "./ops/types.js"
|
|
|
13
11
|
|
|
14
12
|
export * as loot from "./loot/index.js"
|
|
15
13
|
|
|
16
|
-
export * from "./dom/attributes.js"
|
|
17
14
|
export * from "./views/base-element.js"
|
|
18
15
|
export * from "./views/css-reset.js"
|
|
19
16
|
export * from "./views/types.js"
|