sprae 9.1.1 → 10.0.0
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/core.js +66 -83
- package/directive/aria.js +2 -1
- package/directive/class.js +6 -5
- package/directive/data.js +4 -3
- package/directive/default.js +14 -16
- package/directive/each.js +84 -47
- package/directive/fx.js +2 -1
- package/directive/if.js +10 -8
- package/directive/ref.js +7 -7
- package/directive/style.js +4 -3
- package/directive/text.js +4 -3
- package/directive/value.js +2 -1
- package/directive/with.js +16 -0
- package/dist/sprae.js +326 -180
- package/dist/sprae.min.js +1 -1
- package/package.json +5 -4
- package/readme.md +67 -97
- package/signal.js +9 -42
- package/sprae.js +2 -3
- package/store.js +156 -0
- package/directive/scope.js +0 -10
package/dist/sprae.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e,t,r,l,
|
|
1
|
+
var e,t,r,l,n,s=Object.defineProperty,a=Symbol("signals"),o=Symbol("length");function i(t,r){if(!t)return t;if(t[a]&&!r)return t;if(Array.isArray(t))return function(t){let r;if(t[a])return t;let l=e(t.length),n=Array(t.length).fill(null);const s=new Proxy(n,{get:(s,c)=>c===o?l:c===a?n:"length"===c?Array.prototype[r]?l.peek():l.value:(r=c,n[c]?n[c].valueOf():c<n.length?(n[c]=e(i(t[c]))).value:void 0),set(e,t,r){if("length"===t){for(let e=r,t=n.length;e<t;e++)delete s[e];return l.value=n.length=r,!0}return c(n,t,r),t>=l.peek()&&(l.value=n.length=Number(t)+1),!0},deleteProperty:(e,t)=>(u(n,t),!0)});return s}(t);if(t.constructor!==Object)return t;let l=e(Object.values(t).length);r||={};const s=new Proxy(r,{get:(e,t)=>t===o?l:t===a?r:r[t]?.valueOf(),set:(e,t,n,s)=>(s=r[t],c(r,t,n),s||++l.value),deleteProperty:(e,t)=>u(r,t)&&l.value--,ownKeys:()=>(l.value,Reflect.ownKeys(r))});if(t[a])for(let e in t)r[e]=t[a][e];else for(let e in t){const l=Object.getOwnPropertyDescriptor(t,e);l?.get?(r[e]=n(l.get.bind(s)))._set=l.set?.bind(s):(r[e]=null,c(r,e,t[e]))}return s}function c(t,n,s){let a=t[n];if(a)if(s===a.peek());else if(a._set)a._set(s);else if(Array.isArray(s)&&Array.isArray(a.peek())){const e=a.peek();e[o]?r((()=>{l((()=>{let t=0,r=s.length;for(;t<r;t++)e[t]=s[t];e.length=r}))})):a.value=s}else a.value=i(s);else t[n]=a=s?.peek?s:e(i(s))}function u(e,t){const r=e[t];if(r){const l=r[Symbol.dispose];return l&&delete r[Symbol.dispose],delete e[t],l?.(),!0}}var f=Symbol.dispose||=Symbol("dispose"),p={},d=new WeakMap;function y(e,t){let r;e?.[Symbol.iterator]||(e=[e]);for(let l of e)l?.children&&(d.has(l)?Object.assign(d.get(l),t):(r||=i(t||{}),v(l,r),d.has(l)||d.set(l,r)));return r}function v(e,t,r=e.parentNode,l=[]){if(e.attributes)for(let n=0;n<e.attributes.length;){let s=e.attributes[n];if(":"===s.name[0]){e.removeAttribute(s.name);let n=s.name.slice(1).split(":");for(let r of n){let n=p[r]||p.default,a=(n.parse||g)(s.value,g),o=n(e,a,t,r);o&&l.push(o)}if(d.has(e))return;if(e.parentNode!==r)return}else n++}for(let r of[...e.children])v(r,t,e);e.classList?.add("∴"),e[f]=()=>{for(;l.length;)l.pop()();e.classList.remove("∴"),d.delete(e);let t=e.getElementsByClassName("∴");for(;t.length;)t[0][f]?.()}}var h,m={},g=(e,t,r)=>{if(r=m[e=e.trim()])return r;try{r=h(e)}catch(r){throw Object.assign(r,{message:`∴ ${r.message}\n\n${t}${e?`="${e}"\n\n`:""}`,expr:e})}return m[e]=r};y.use=s=>{s.signal&&function(s){e=s.signal,t=s.effect,n=s.computed,l=s.batch||(e=>e()),r=s.untracked||l}(s),s.compile&&(h=s.compile)};var b,k,A={};((e,t)=>{for(var r in t)s(e,r,{get:t[r],enumerable:!0})})(A,{batch:()=>N,computed:()=>O,effect:()=>w,signal:()=>S,untracked:()=>x});var S=(e,t,r=new Set)=>((t={get value(){return b?.deps.push(r.add(b)),e},set value(t){if(t!==e){e=t;for(let e of r)k?k.add(e):e()}},peek:()=>e}).toJSON=t.then=t.toString=t.valueOf=()=>t.value,t),w=(e,t,r,l)=>(l=(r=l=>{t?.call?.(),l=b,b=r;try{t=e()}finally{b=l}}).deps=[],r(),e=>{for(t?.call?.();e=l.pop();)e.delete(r)}),O=(e,t=S(),r,l)=>((r={get value(){return l||=w((()=>t.value=e())),t.value},peek:t.peek}).toJSON=r.then=r.toString=r.valueOf=()=>r.value,r),N=e=>{let t=k;t||(k=new Set);try{e()}finally{if(!t){t=k,k=null;for(const e of t)e()}}},x=(e,t,r)=>(t=b,b=null,r=e(),b=t,r),j=(e,t,r,l=null,{remove:n,insert:s}=j)=>{let a,o,i,c=0,u=new Set(r);for(;i=t[c++];)u.has(i)?a=a||i:n(i,e);for(a=a||l,c=0;i=r[c++];)o=a?a.nextSibling:l,a===i?a=o:(r[c]===o&&(a=o),s(i,a,e));return r};j.insert=(e,t,r)=>r.insertBefore(e,t),j.remove=(e,t)=>t.removeChild(e);var C=j,E=Symbol(":each");p.each=(e,[l,s,i],c)=>{const u=e[E]=document.createTextNode("");e.replaceWith(u);let f,p,d=0;const v=n((()=>{p=null;let e=i(c);return"number"==typeof e&&(e=Array.from({length:e},((e,t)=>t+1))),e?.constructor===Object&&(p=Object.keys(e),e=Object.values(e)),e||[]})),h=()=>{r((()=>{let t=0,r=v.value,n=r.length;if(f&&!f[o]){for(let e of f[a]||[])e[Symbol.dispose]();f=null,d=0}if(n<d)f.length=n;else{if(f)for(;t<d;t++)f[t]=r[t];else f=r;for(;t<n;t++){f[t]=r[t];let n=t,o=Object.create(c,{[l]:{get:()=>f[n]},[s]:{value:p?p[n]:n}}),i=(e.content||e).cloneNode(!0),d=e.content?[...i.childNodes]:[i];u.before(i),y(d,o),((f[a]||=[])[t]||={})[Symbol.dispose]=()=>{for(let e of d)e[Symbol.dispose](),e.remove()}}}d=n}))};let m=0;return t((()=>{f||v.value[o]?.value,m?m++:(h(),queueMicrotask((()=>(m&&h(),m=0))))}))},p.each.parse=(e,t)=>{let[r,l]=e.split(/\s+in\s+/),[n,s="$"]=r.split(/\s*,\s*/);return[n,s,t(l)]};var $=Symbol("if");p.if=(e,r,l)=>{let n,s,a,o=e.parentNode,i=e.nextElementSibling,c=document.createTextNode(""),u=[];return e.after(c),e.content?(n=u,e.remove(),s=[...e.content.childNodes]):s=n=[e],i?.hasAttribute(":else")?(i.removeAttribute(":else"),i.hasAttribute(":if")?a=u:(i.remove(),a=i.content?[...i.content.childNodes]:[i])):a=u,t((()=>{const t=r(l)?s:e[$]?u:a;if(i&&(i[$]=t===s),n!=t){n[0]?.[E]&&(n=[n[0][E]]);for(let e of n)e.remove();n=t;for(let e of n)o.insertBefore(e,c),y(e,l)}}))},p.default=(e,r,l,n)=>{let s,a=n.startsWith("on")&&n.slice(2);return t(a?()=>(s?.(),s=P(e,a,r(l))):()=>{let t=r(l);if(n)L(e,n,B(t,l));else for(let r in t)L(e,K(r),B(t[r],l))})};var P=(e,t,r=(()=>{}))=>{const l={evt:"",target:e,test:()=>!0};l.evt=t.replace(/\.(\w+)?-?([-\w]+)?/g,((e,t,r="")=>(l.test=T[t]?.(l,...r.split("-"))||l.test,"")));const{evt:n,target:s,test:a,defer:o,stop:i,prevent:c,...u}=l;o&&(r=o(r));const f=e=>a(e)&&(i&&e.stopPropagation(),c&&e.preventDefault(),r.call(s,e));return s.addEventListener(n,f,u),()=>s.removeEventListener(n,f,u)},T={prevent(e){e.prevent=!0},stop(e){e.stop=!0},once(e){e.once=!0},passive(e){e.passive=!0},capture(e){e.capture=!0},window(e){e.target=window},document(e){e.target=document},throttle(e,t){e.defer=e=>_(e,t?Number(t)||0:108)},debounce(e,t){e.defer=e=>D(e,t?Number(t)||0:108)},outside:e=>t=>{let r=e.target;return!(r.contains(t.target)||!1===t.target.isConnected||r.offsetWidth<1&&r.offsetHeight<1)},self:e=>t=>t.target===e.target,ctrl:(e,...t)=>e=>W.ctrl(e)&&t.every((t=>W[t]?W[t](e):e.key===t)),shift:(e,...t)=>e=>W.shift(e)&&t.every((t=>W[t]?W[t](e):e.key===t)),alt:(e,...t)=>e=>W.alt(e)&&t.every((t=>W[t]?W[t](e):e.key===t)),meta:(e,...t)=>e=>W.meta(e)&&t.every((t=>W[t]?W[t](e):e.key===t)),arrow:()=>W.arrow,enter:()=>W.enter,escape:()=>W.escape,tab:()=>W.tab,space:()=>W.space,backspace:()=>W.backspace,delete:()=>W.delete,digit:()=>W.digit,letter:()=>W.letter,character:()=>W.character},W={ctrl:e=>e.ctrlKey||"Control"===e.key||"Ctrl"===e.key,shift:e=>e.shiftKey||"Shift"===e.key,alt:e=>e.altKey||"Alt"===e.key,meta:e=>e.metaKey||"Meta"===e.key||"Command"===e.key,arrow:e=>e.key.startsWith("Arrow"),enter:e=>"Enter"===e.key,escape:e=>e.key.startsWith("Esc"),tab:e=>"Tab"===e.key,space:e=>" "===e.key||"Space"===e.key||" "===e.key,backspace:e=>"Backspace"===e.key,delete:e=>"Delete"===e.key,digit:e=>/^\d$/.test(e.key),letter:e=>/^[a-zA-Z]$/.test(e.key),character:e=>/^\S$/.test(e.key)},L=(e,t,r)=>{null==r||!1===r?e.removeAttribute(t):e.setAttribute(t,!0===r?"":"number"==typeof r||"string"==typeof r?r:"")},_=(e,t)=>{let r,l,n=s=>{r=!0,setTimeout((()=>{if(r=!1,l)return l=!1,n(s),e(s)}),t)};return t=>r?l=!0:(n(t),e(t))},D=(e,t)=>{let r;return l=>{clearTimeout(r),r=setTimeout((()=>{r=null,e(l)}),t)}},K=e=>e.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g,(e=>"-"+e.toLowerCase())),B=(e,t)=>e?.replace?e.replace(/\$<([^>]+)>/g,((e,r)=>t[r]??"")):e;p.ref=(e,t,r)=>{Object.defineProperty(r,B(t,r),{value:e})},p.ref.parse=e=>e,p.with=(e,r,l)=>{let n,s;return t((()=>{s=r(l),Object.assign(n||=y(e,i(s,Object.create(l[a]))),s)}))},p.html=(e,t,r)=>{let l=t(r);if(!l)return;let n=(l.content||l).cloneNode(!0);e.replaceChildren(n),y(e,r)},p.text=(e,r,l)=>(e.content&&e.replaceWith(e=document.createTextNode("")),t((()=>{let t=r(l);e.textContent=null==t?"":t}))),p.class=(e,r,l)=>{let n=new Set;return t((()=>{let t=r(l),s=new Set;t&&("string"==typeof t?B(t,l).split(" ").map((e=>s.add(e))):Array.isArray(t)?t.map((e=>(e=B(e,l))&&s.add(e))):Object.entries(t).map((([e,t])=>t&&s.add(e))));for(let t of n)s.has(t)?s.delete(t):e.classList.remove(t);for(let t of n=s)e.classList.add(t)}))},p.style=(e,r,l)=>{let n=e.getAttribute("style")||"";return n.endsWith(";")||(n+="; "),t((()=>{let t=r(l);if("string"==typeof t)e.setAttribute("style",n+B(t,l));else{e.setAttribute("style",n);for(let r in t)e.style.setProperty(r,B(t[r],l))}}))},p.value=(e,r,l)=>{let n,s,a="text"===e.type||""===e.type?t=>e.setAttribute("value",e.value=null==t?"":t):"TEXTAREA"===e.tagName||"text"===e.type||""===e.type?t=>(n=e.selectionStart,s=e.selectionEnd,e.setAttribute("value",e.value=null==t?"":t),n&&e.setSelectionRange(n,s)):"checkbox"===e.type?t=>(e.checked=t,L(e,"checked",t)):"select-one"===e.type?t=>{for(let t in e.options)t.removeAttribute("selected");e.value=t,e.selectedOptions[0]?.setAttribute("selected","")}:t=>e.value=t;return t((()=>a(r(l))))},p.fx=(e,r,l)=>t((()=>r(l))),y.use(A),y.use({compile:e=>y.constructor("__scope",`with (__scope) { return ${e} };`)}),y.use({swap:C});var M=y;export{M as default};
|
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sprae",
|
|
3
3
|
"description": "DOM microhydration.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "10.0.0",
|
|
5
5
|
"main": "./sprae.js",
|
|
6
6
|
"module": "./sprae.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"files": [
|
|
9
9
|
"core.js",
|
|
10
10
|
"sprae.js",
|
|
11
|
+
"store.js",
|
|
11
12
|
"signal.js",
|
|
12
13
|
"directive",
|
|
13
14
|
"dist"
|
|
14
15
|
],
|
|
15
16
|
"dependencies": {
|
|
16
|
-
"
|
|
17
|
+
"ulive": "^1.0.2"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"@preact/signals": "^1.1.3",
|
|
@@ -22,10 +23,10 @@
|
|
|
22
23
|
"esbuild": "^0.15.14",
|
|
23
24
|
"hyperf": "^1.6.2",
|
|
24
25
|
"jsdom": "^21.1.0",
|
|
25
|
-
"
|
|
26
|
+
"signal-polyfill": "^0.1.1",
|
|
27
|
+
"subscript": "^8.3.5",
|
|
26
28
|
"terser": "^5.15.1",
|
|
27
29
|
"tst": "^7.1.1",
|
|
28
|
-
"ulive": "^1.0.1",
|
|
29
30
|
"usignal": "^0.9.0",
|
|
30
31
|
"wait-please": "^3.1.0"
|
|
31
32
|
},
|
package/readme.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> DOM tree microhydration
|
|
4
4
|
|
|
5
|
-
_Sprae_ is
|
|
6
|
-
It provides `:`-attributes for inline markup logic with signals reactivity.<br/>
|
|
5
|
+
_Sprae_ is open & minimalistic progressive enhancement framework.<br/>
|
|
7
6
|
Perfect for small-scale websites, static pages, landings, prototypes, or lightweight UI.<br/>
|
|
8
7
|
|
|
9
8
|
|
|
@@ -15,17 +14,17 @@ Perfect for small-scale websites, static pages, landings, prototypes, or lightwe
|
|
|
15
14
|
</div>
|
|
16
15
|
|
|
17
16
|
<script type="module">
|
|
18
|
-
import sprae
|
|
17
|
+
import sprae from 'sprae'
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
sprae(container, { user: { name } })
|
|
19
|
+
// init
|
|
20
|
+
const state = sprae(container, { user: { name: 'Kitty' } })
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
// update
|
|
23
|
+
state.user.name = 'Dolly'
|
|
24
24
|
</script>
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Sprae evaluates `:`-directives and evaporates them,
|
|
28
|
-
|
|
27
|
+
Sprae evaluates `:`-directives and evaporates them, returning reactive state.
|
|
29
28
|
|
|
30
29
|
## Directives
|
|
31
30
|
|
|
@@ -43,21 +42,17 @@ Control flow of elements.
|
|
|
43
42
|
```
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
#### `:each="item, index in items"`
|
|
45
|
+
#### `:each="item, index? in items"`
|
|
47
46
|
|
|
48
|
-
Multiply element.
|
|
47
|
+
Multiply element.
|
|
49
48
|
|
|
50
49
|
```html
|
|
51
50
|
<ul><li :each="item in items" :text="item"/></ul>
|
|
52
51
|
|
|
53
52
|
<!-- cases -->
|
|
54
|
-
<li :each="item, idx in
|
|
55
|
-
<li :each="
|
|
56
|
-
<li :each="idx in number" />
|
|
57
|
-
|
|
58
|
-
<!-- by condition -->
|
|
59
|
-
<li :if="items" :each="item in items" :text="item" />
|
|
60
|
-
<li :else>Empty list</li>
|
|
53
|
+
<li :each="item, idx in array" />
|
|
54
|
+
<li :each="value, key in object" />
|
|
55
|
+
<li :each="count, idx in number" />
|
|
61
56
|
|
|
62
57
|
<!-- fragment -->
|
|
63
58
|
<template :each="item in items">
|
|
@@ -82,22 +77,28 @@ Welcome, <template :text="user.name" />.
|
|
|
82
77
|
|
|
83
78
|
#### `:class="value"`
|
|
84
79
|
|
|
85
|
-
Set class value
|
|
80
|
+
Set class value.
|
|
86
81
|
|
|
87
82
|
```html
|
|
88
|
-
<!--
|
|
83
|
+
<!-- appends class -->
|
|
84
|
+
<div class="foo" :class="bar"></div>
|
|
85
|
+
|
|
86
|
+
<!-- interpolation -->
|
|
89
87
|
<div :class="'foo $<bar>'"></div>
|
|
90
88
|
|
|
91
|
-
<!-- array/object a-la clsx -->
|
|
89
|
+
<!-- array/object, a-la clsx -->
|
|
92
90
|
<div :class="[foo && 'foo', {bar: bar}]"></div>
|
|
93
91
|
```
|
|
94
92
|
|
|
95
93
|
#### `:style="value"`
|
|
96
94
|
|
|
97
|
-
Set style value
|
|
95
|
+
Set style value.
|
|
98
96
|
|
|
99
97
|
```html
|
|
100
|
-
<!--
|
|
98
|
+
<!-- extends style -->
|
|
99
|
+
<div style="foo: bar" :style="'baz: qux'">
|
|
100
|
+
|
|
101
|
+
<!-- interpolation -->
|
|
101
102
|
<div :style="'foo: $<bar>'"></div>
|
|
102
103
|
|
|
103
104
|
<!-- object -->
|
|
@@ -109,16 +110,19 @@ Set style value, extends existing `style`.
|
|
|
109
110
|
|
|
110
111
|
#### `:value="value"`
|
|
111
112
|
|
|
112
|
-
Set value of an input, textarea or select.
|
|
113
|
+
Set value of an input, textarea or select.
|
|
113
114
|
|
|
114
115
|
```html
|
|
115
116
|
<input :value="value" />
|
|
116
117
|
<textarea :value="value" />
|
|
117
118
|
|
|
118
|
-
<!-- selects right option -->
|
|
119
|
+
<!-- selects right option & handles selected attr -->
|
|
119
120
|
<select :value="selected">
|
|
120
121
|
<option :each="i in 5" :value="i" :text="i"></option>
|
|
121
122
|
</select>
|
|
123
|
+
|
|
124
|
+
<!-- handles checked attr -->
|
|
125
|
+
<input type="checkbox" :value="checked" />
|
|
122
126
|
```
|
|
123
127
|
|
|
124
128
|
#### `:[prop]="value"`, `:="values"`
|
|
@@ -135,33 +139,32 @@ Set any attribute(s).
|
|
|
135
139
|
<input :="{ id: name, name, type: 'text', value }" />
|
|
136
140
|
```
|
|
137
141
|
|
|
138
|
-
#### `:
|
|
142
|
+
#### `:with="values"`
|
|
139
143
|
|
|
140
|
-
Define
|
|
144
|
+
Define values for a subtree.
|
|
141
145
|
|
|
142
146
|
```html
|
|
143
|
-
<x :
|
|
144
|
-
|
|
145
|
-
<y :scope="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
147
|
+
<x :with="{ foo: signal('bar') }">
|
|
148
|
+
<y :with="{ baz: 'qux' }" :text="foo + baz"></y>
|
|
146
149
|
</x>
|
|
147
150
|
```
|
|
148
151
|
|
|
149
152
|
#### `:ref="name"`
|
|
150
153
|
|
|
151
|
-
Expose element
|
|
154
|
+
Expose element with `name`.
|
|
152
155
|
|
|
153
156
|
```html
|
|
154
157
|
<textarea :ref="text" placeholder="Enter text..."></textarea>
|
|
155
158
|
|
|
156
159
|
<!-- iterable items -->
|
|
157
160
|
<li :each="item in items" :ref="item">
|
|
158
|
-
<input :onfocus..onblur
|
|
161
|
+
<input :onfocus..onblur="e => (item.classList.add('editing'), e => item.classList.remove('editing'))"/>
|
|
159
162
|
</li>
|
|
160
163
|
```
|
|
161
164
|
|
|
162
165
|
#### `:fx="code"`
|
|
163
166
|
|
|
164
|
-
Run effect, not changing any attribute
|
|
167
|
+
Run effect, not changing any attribute.
|
|
165
168
|
|
|
166
169
|
```html
|
|
167
170
|
<div :fx="a.value ? foo() : bar()" />
|
|
@@ -172,7 +175,7 @@ Run effect, not changing any attribute.<br/>Optional cleanup is called in-betwee
|
|
|
172
175
|
|
|
173
176
|
#### `:on[event]="handler"`
|
|
174
177
|
|
|
175
|
-
Attach event(s) listener with
|
|
178
|
+
Attach event(s) listener with optional modifiers.
|
|
176
179
|
|
|
177
180
|
```html
|
|
178
181
|
<input type="checkbox" :onchange="e => isChecked = e.target.value">
|
|
@@ -183,7 +186,7 @@ Attach event(s) listener with possible modifiers.
|
|
|
183
186
|
<!-- events sequence -->
|
|
184
187
|
<button :onfocus..onblur="e => ( handleFocus(), e => handleBlur())">
|
|
185
188
|
|
|
186
|
-
<!--
|
|
189
|
+
<!-- modifiers -->
|
|
187
190
|
<button :onclick.throttle-500="handler">Not too often</button>
|
|
188
191
|
```
|
|
189
192
|
|
|
@@ -211,7 +214,7 @@ Hello, <template :html="user.name">Guest</template>.
|
|
|
211
214
|
|
|
212
215
|
<!-- instantiate template -->
|
|
213
216
|
<template :ref="tpl"><span :text="foo"></span></template>
|
|
214
|
-
<div :html="tpl" :
|
|
217
|
+
<div :html="tpl" :with="{foo:'bar'}">...inserted here...</div>
|
|
215
218
|
```
|
|
216
219
|
|
|
217
220
|
#### `:data="values"` 🔌
|
|
@@ -267,29 +270,37 @@ Trigger when element is connected / disconnected from DOM.
|
|
|
267
270
|
|
|
268
271
|
## Signals
|
|
269
272
|
|
|
270
|
-
Sprae
|
|
273
|
+
Sprae can take signal values. Signals provider can be switched to any preact-flavored implementation:
|
|
271
274
|
|
|
272
275
|
```js
|
|
273
|
-
import sprae
|
|
276
|
+
import sprae from 'sprae';
|
|
277
|
+
import { signal, computed, effect, batch, untracked } from 'sprae/signal';
|
|
274
278
|
import * as signals from '@preact/signals-core';
|
|
275
279
|
|
|
280
|
+
// switch provider to @preact/signals-core
|
|
276
281
|
sprae.use(signals);
|
|
277
282
|
|
|
278
|
-
|
|
283
|
+
// use signal as state value
|
|
284
|
+
const name = signal('Kitty')
|
|
285
|
+
sprae(el, { name });
|
|
286
|
+
|
|
287
|
+
// update state
|
|
288
|
+
name.value = 'Dolly';
|
|
279
289
|
```
|
|
280
290
|
|
|
281
291
|
Provider | Size | Feature
|
|
282
292
|
:---|:---|:---
|
|
283
|
-
[`ulive`](https://ghub.io/ulive) | 350b | Minimal implementation, basic performance, good for small states
|
|
284
|
-
[`@webreflection/signal`](https://ghib.io/@webreflection/signal) | 531b | Class-based, better performance, good for small-medium states
|
|
285
|
-
[`usignal`](https://ghib.io/usignal) | 850b | Class-based with optimizations, good for medium states
|
|
286
|
-
[`@preact/signals-core`](https://ghub.io/@preact/signals-core) | 1.47kb | Best performance, good for any states
|
|
287
|
-
[`signal-polyfill`](https://github.com/tc39/proposal-signals) | 2.5kb |
|
|
293
|
+
[`ulive`](https://ghub.io/ulive) (default) | 350b | Minimal implementation, basic performance, good for small states.
|
|
294
|
+
[`@webreflection/signal`](https://ghib.io/@webreflection/signal) | 531b | Class-based, better performance, good for small-medium states.
|
|
295
|
+
[`usignal`](https://ghib.io/usignal) | 850b | Class-based with optimizations, good for medium states.
|
|
296
|
+
[`@preact/signals-core`](https://ghub.io/@preact/signals-core) | 1.47kb | Best performance, good for any states, industry standard.
|
|
297
|
+
[`signal-polyfill`](https://github.com/tc39/proposal-signals) | 2.5kb | Proposal signals. Use via [adapter](https://gist.github.com/dy/bbac687464ccf5322ab0e2fd0680dc4d).
|
|
288
298
|
|
|
289
299
|
|
|
290
300
|
## Evaluator
|
|
291
301
|
|
|
292
|
-
Expressions use _new Function_ as default evaluator, which is fast & compact way, but violates "unsafe-eval" CSP.
|
|
302
|
+
Expressions use _new Function_ as default evaluator, which is fast & compact way, but violates "unsafe-eval" CSP.
|
|
303
|
+
To make eval stricter & safer, as well as sandbox expressions, an alternative evaluator can be used, eg. _justin_:
|
|
293
304
|
|
|
294
305
|
```js
|
|
295
306
|
import sprae from 'sprae'
|
|
@@ -298,7 +309,7 @@ import justin from 'subscript/justin'
|
|
|
298
309
|
sprae.use({compile: justin}) // set up justin as default compiler
|
|
299
310
|
```
|
|
300
311
|
|
|
301
|
-
[_Justin_](https://github.com/dy/subscript?tab=readme-ov-file#justin) is minimal JS subset
|
|
312
|
+
[_Justin_](https://github.com/dy/subscript?tab=readme-ov-file#justin) is minimal JS subset that avoids "unsafe-eval" CSP and provides sandboxing.
|
|
302
313
|
|
|
303
314
|
###### Operators:
|
|
304
315
|
|
|
@@ -313,84 +324,43 @@ sprae.use({compile: justin}) // set up justin as default compiler
|
|
|
313
324
|
`true false null undefined NaN`
|
|
314
325
|
|
|
315
326
|
|
|
316
|
-
##
|
|
327
|
+
## Custom Build
|
|
317
328
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
```js
|
|
321
|
-
import sprae, { directive } from 'sprae/core.js'
|
|
322
|
-
|
|
323
|
-
// define custom directive
|
|
324
|
-
directive.id = (el, evaluate, state) => {
|
|
325
|
-
return () => el.id = evaluate(state) // return update function
|
|
326
|
-
}
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
## Custom build
|
|
330
|
-
|
|
331
|
-
_Sprae_ can be tailored to project needs via `sprae/core` for performance, size or compatibility purposes:
|
|
329
|
+
_Sprae_ can be tailored to project needs via `sprae/core`:
|
|
332
330
|
|
|
333
331
|
```js
|
|
334
332
|
// sprae.custom.js
|
|
335
|
-
import sprae, { directive } from 'sprae/core
|
|
333
|
+
import sprae, { directive } from 'sprae/core'
|
|
334
|
+
import { effect } from 'sprae/signal'
|
|
336
335
|
import * as signals from '@preact/signals'
|
|
337
336
|
import compile from 'subscript'
|
|
338
|
-
import diff from 'udomdiff
|
|
339
337
|
|
|
340
338
|
// include directives
|
|
341
339
|
import 'sprae/directive/if.js'
|
|
342
340
|
import 'sprae/directive/text.js'
|
|
343
341
|
|
|
342
|
+
// custom directive :id="expression"
|
|
343
|
+
directive.id = (el, evaluate, state) => {
|
|
344
|
+
effect(() => el.id = evaluate(state))
|
|
345
|
+
}
|
|
346
|
+
|
|
344
347
|
// configure signals
|
|
345
348
|
sprae.use(signals)
|
|
346
349
|
|
|
347
350
|
// configure compiler
|
|
348
351
|
sprae.use({ compile })
|
|
349
|
-
|
|
350
|
-
// configure dom differ
|
|
351
|
-
sprae.use({ swap: (parent, from, to, before) => udomdiff(parent, from, to, node=>node, before) })
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
<!--
|
|
355
|
-
### DOM diffing
|
|
356
|
-
|
|
357
|
-
DOM diffing uses [swapdom](https://github.com/dy/swapdom), but can be reconfigured to [list-difference](https://github.com/paldepind/list-difference/), [udomdiff](https://github.com/WebReflection/udomdiff), [domdiff](https://github.com/WebReflection/domdiff), or any other ([benchmark](https://github.com/luwes/js-diff-benchmark)):
|
|
358
|
-
|
|
359
|
-
```js
|
|
360
|
-
import sprae from 'sprae';
|
|
361
|
-
import domdiff from 'list-difference';
|
|
362
|
-
|
|
363
|
-
// swap(parentNode, prevEls, newEls, endNode?)
|
|
364
|
-
sprae.use({ swap: domdiff });
|
|
365
352
|
```
|
|
366
|
-
-->
|
|
367
|
-
|
|
368
353
|
|
|
369
354
|
<!-- ## Dispose
|
|
370
355
|
|
|
371
356
|
To destroy state and detach sprae handlers, call `element[Symbol.dispose]()`. -->
|
|
372
357
|
|
|
373
358
|
|
|
374
|
-
<!--
|
|
375
|
-
## v9 changes
|
|
376
|
-
|
|
377
|
-
* No autoinit → use manual init via `import sprae from 'sprae'; sprae(document.body, state)`.
|
|
378
|
-
* No default globals (`console`, `setTimeout` etc) - pass to state if required.
|
|
379
|
-
* ``:class="`abc ${def}`"`` → `:class="'abc $<def>'"` (_justin_)
|
|
380
|
-
* `:with={x:'x'}` -> `:scope={x:'x'}`
|
|
381
|
-
* No reactive store → use signals for reactive values.
|
|
382
|
-
* `:render="tpl"` → `:html="tpl"`
|
|
383
|
-
* `@click="event.target"` → `:onclick="event => event.target"`
|
|
384
|
-
* Async props / events are not supported, pass async functions via state.
|
|
385
|
-
* Directives order matters, eg. `<a :if :each :scope />` !== `<a :scope :each :if />`
|
|
386
|
-
* Only one directive per `<template>`, eg. `<template :each />`, not `<template :if :each/>`
|
|
387
|
-
-->
|
|
388
|
-
|
|
389
359
|
## Justification
|
|
390
360
|
|
|
391
|
-
[Template-parts](https://github.com/dy/template-parts)
|
|
361
|
+
[Template-parts](https://github.com/dy/template-parts) is stuck with native HTML quirks ([parsing table](https://github.com/github/template-parts/issues/24), [SVG attributes](https://github.com/github/template-parts/issues/25), [liquid syntax](https://shopify.github.io/liquid/tags/template/#raw) conflict etc). [Alpine](https://github.com/alpinejs/alpine) / [petite-vue](https://github.com/vuejs/petite-vue) / [lucia](https://github.com/aidenyabi/lucia) escape native HTML quirks, but have excessive API (`:`, `x-`, `{}`, `@`, `$`) and tend to [self-encapsulate](https://github.com/alpinejs/alpine/discussions/3223).
|
|
392
362
|
|
|
393
|
-
_Sprae_ holds open & minimalistic philosophy, combining _`:`-directives_ with _signals_.
|
|
363
|
+
_Sprae_ holds open & minimalistic philosophy, combining _`:`-directives_ with emerging _signals_.
|
|
394
364
|
|
|
395
365
|
<!--
|
|
396
366
|
| | [AlpineJS](https://github.com/alpinejs/alpine) | [Petite-Vue](https://github.com/vuejs/petite-vue) | Sprae |
|
package/signal.js
CHANGED
|
@@ -1,43 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
let
|
|
1
|
+
// signals adapter - allows switching signals implementation and not depend on core
|
|
2
|
+
export let signal, effect, untracked, batch, computed;
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (val === v) return
|
|
12
|
-
v = val;
|
|
13
|
-
for (let sub of obs) sub(val); // notify effects
|
|
14
|
-
},
|
|
15
|
-
peek() { return v },
|
|
16
|
-
},
|
|
17
|
-
s.toJSON = s.then = s.toString = s.valueOf = () => s.value,
|
|
18
|
-
s
|
|
19
|
-
),
|
|
20
|
-
effect = (fn, teardown, run, deps) => (
|
|
21
|
-
run = (prev) => {
|
|
22
|
-
teardown?.call?.();
|
|
23
|
-
prev = current, current = run;
|
|
24
|
-
try { teardown = fn(); } finally { current = prev; }
|
|
25
|
-
},
|
|
26
|
-
deps = run.deps = [],
|
|
27
|
-
|
|
28
|
-
run(),
|
|
29
|
-
(dep) => { teardown?.call?.(); while (dep = deps.pop()) dep.delete(run); }
|
|
30
|
-
),
|
|
31
|
-
computed = (fn, s = signal(), c, e) => (
|
|
32
|
-
c = {
|
|
33
|
-
get value() {
|
|
34
|
-
e ||= effect(() => s.value = fn());
|
|
35
|
-
return s.value
|
|
36
|
-
},
|
|
37
|
-
peek: s.peek
|
|
38
|
-
},
|
|
39
|
-
c.toJSON = c.then = c.toString = c.valueOf = () => c.value,
|
|
40
|
-
c
|
|
41
|
-
),
|
|
42
|
-
batch = (fn) => fn(),
|
|
43
|
-
untracked = (fn, prev, v) => (prev = current, current = null, v = fn(), current = prev, v);
|
|
4
|
+
export function use(s) {
|
|
5
|
+
signal = s.signal
|
|
6
|
+
effect = s.effect
|
|
7
|
+
computed = s.computed
|
|
8
|
+
batch = s.batch || (fn => fn())
|
|
9
|
+
untracked = s.untracked || batch
|
|
10
|
+
}
|
package/sprae.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import sprae from './core.js'
|
|
2
2
|
|
|
3
|
-
import * as signals from '
|
|
3
|
+
import * as signals from 'ulive'
|
|
4
4
|
import swap from 'swapdom/deflate'
|
|
5
5
|
|
|
6
6
|
// default directives
|
|
7
7
|
import './directive/if.js'
|
|
8
8
|
import './directive/each.js'
|
|
9
9
|
import './directive/ref.js'
|
|
10
|
-
import './directive/
|
|
10
|
+
import './directive/with.js'
|
|
11
11
|
import './directive/html.js'
|
|
12
12
|
import './directive/text.js'
|
|
13
13
|
import './directive/class.js'
|
|
@@ -26,4 +26,3 @@ sprae.use({ compile: expr => sprae.constructor(`__scope`, `with (__scope) { retu
|
|
|
26
26
|
sprae.use({ swap })
|
|
27
27
|
|
|
28
28
|
export default sprae
|
|
29
|
-
export * from './core.js'
|