situs-kit 0.1.8 → 0.2.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/README.md +4 -118
- package/dist/chunk-0t3a7kj5.js +2 -0
- package/dist/chunk-qjsknche.js +2 -0
- package/dist/chunk-se6yyp59.js +2 -0
- package/dist/chunk-xcfcqftx.js +2 -0
- package/dist/dom/index.d.ts +74 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/math/index.d.ts +40 -0
- package/dist/smooth-scroll/index.d.ts +83 -0
- package/dist/smooth-scroll/index.js +1 -0
- package/dist/split-text/index.d.ts +1 -1
- package/dist/split-text/index.js +1 -1
- package/dist/ticker/index.d.ts +9 -0
- package/dist/ticker/index.js +1 -0
- package/docs/smooth-scroll.md +82 -0
- package/docs/split-text.md +181 -0
- package/docs/ticker.md +30 -0
- package/package.json +11 -2
- package/dist/chunk-8pnv9yxr.js +0 -2
package/README.md
CHANGED
|
@@ -8,125 +8,11 @@ A zero-dependency creative developer toolkit
|
|
|
8
8
|
npm install situs-kit
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Modules
|
|
12
12
|
|
|
13
|
-
Split text into characters, words, and lines
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
import { SplitText } from "situs-kit/split-text";
|
|
19
|
-
|
|
20
|
-
const split = new SplitText("#my-text", {
|
|
21
|
-
type: ["chars", "words", "lines"],
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
split.chars; // HTMLElement[]
|
|
25
|
-
split.words; // HTMLElement[]
|
|
26
|
-
split.lines; // HTMLElement[]
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Options
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
new SplitText(element, {
|
|
33
|
-
type: ["chars", "words", "lines"], // which levels to split
|
|
34
|
-
tag: "span", // wrapper element tag
|
|
35
|
-
mask: ["chars", "lines"], // which levels to mask
|
|
36
|
-
resize: true, // auto-reflow lines on resize
|
|
37
|
-
style: {
|
|
38
|
-
chars: { color: "red" },
|
|
39
|
-
words: {},
|
|
40
|
-
lines: {},
|
|
41
|
-
mask: {},
|
|
42
|
-
},
|
|
43
|
-
class: {
|
|
44
|
-
chars: "my-char",
|
|
45
|
-
words: "my-word",
|
|
46
|
-
lines: "my-line",
|
|
47
|
-
mask: "my-mask",
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
| Option | Type | Default | Description |
|
|
53
|
-
|--------|------|---------|-------------|
|
|
54
|
-
| `type` | `string \| string[]` | `["chars", "words", "lines"]` | Split granularity |
|
|
55
|
-
| `tag` | `string` | `"span"` | Wrapper element tag |
|
|
56
|
-
| `mask` | `boolean \| SplitType[]` | `false` | Create overflow-hidden wrappers per split type |
|
|
57
|
-
| `resize` | `boolean` | `true` | Auto-reflow lines on window resize |
|
|
58
|
-
| `style` | `object` | — | Inline styles per split type |
|
|
59
|
-
| `class` | `object` | — | CSS classes per split type |
|
|
60
|
-
|
|
61
|
-
### Properties
|
|
62
|
-
|
|
63
|
-
| Property | Type | Description |
|
|
64
|
-
|----------|------|-------------|
|
|
65
|
-
| `dom` | `HTMLElement` | The original element |
|
|
66
|
-
| `chars` | `HTMLElement[]` | Character elements |
|
|
67
|
-
| `words` | `HTMLElement[]` | Word elements |
|
|
68
|
-
| `lines` | `HTMLElement[]` | Line elements |
|
|
69
|
-
| `masks` | `{ chars, words, lines }` | Mask wrapper elements |
|
|
70
|
-
|
|
71
|
-
### Methods
|
|
72
|
-
|
|
73
|
-
#### `reflow()`
|
|
74
|
-
|
|
75
|
-
Recalculate line groupings without re-splitting. Called automatically on resize.
|
|
76
|
-
|
|
77
|
-
```ts
|
|
78
|
-
split.reflow();
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
#### `revert()`
|
|
82
|
-
|
|
83
|
-
Restore the element to its original HTML.
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
split.revert();
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
#### `destroy()`
|
|
90
|
-
|
|
91
|
-
Remove resize observers and event listeners.
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
split.destroy();
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Styling with CSS
|
|
98
|
-
|
|
99
|
-
Split elements have data attributes for easy targeting:
|
|
100
|
-
|
|
101
|
-
```css
|
|
102
|
-
[data-char] { display: inline-block; }
|
|
103
|
-
[data-word] { display: inline-block; }
|
|
104
|
-
[data-line] { display: block; }
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### HTML Preservation
|
|
108
|
-
|
|
109
|
-
Inline elements like `<a>`, `<em>`, `<strong>`, `<u>`, `<i>`, and `<b>` are preserved and cloned around split elements. Opaque elements like `<svg>`, `<img>`, and `<br>` are treated as single units.
|
|
110
|
-
|
|
111
|
-
```html
|
|
112
|
-
<!-- Input -->
|
|
113
|
-
<p id="text">Hello <strong>world</strong></p>
|
|
114
|
-
|
|
115
|
-
<!-- After split, <strong> wraps are preserved -->
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Masking
|
|
119
|
-
|
|
120
|
-
Masks wrap split elements in `overflow: hidden` containers, useful for reveal animations. Like `type`, you specify which split levels to mask:
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
const split = new SplitText("#text", {
|
|
124
|
-
type: ["words", "lines"],
|
|
125
|
-
mask: ["lines"], // only mask lines
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
split.masks.lines; // HTMLElement[] - overflow:hidden wrappers
|
|
129
|
-
```
|
|
13
|
+
- [SplitText](./docs/split-text.md) — Split text into characters, words, and lines with HTML preservation
|
|
14
|
+
- [SmoothScroll](./docs/smooth-scroll.md) — Lerp-based smooth scroll with native touch/mobile support
|
|
15
|
+
- [Ticker](./docs/ticker.md) — Shared RAF loop manager for frame-synced animations
|
|
130
16
|
|
|
131
17
|
## License
|
|
132
18
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function R(j,k){return typeof globalThis.getComputedStyle==="function"?globalThis.getComputedStyle(j,k??null):null}var K=["whe","mou","tou","poi"];function J(j){return K.includes(j.slice(0,3))}function L(j,k){if(!k)return J(j)?{passive:!1}:!1;if(typeof k==="object"&&k.passive===void 0&&J(j))return{...k,passive:!1};return k}function M(j){if(!j)return!1;if(typeof j==="object")return j.capture??!1;return j}function V(j,k,w,B,D){let E=k==="a"?"addEventListener":"removeEventListener",G=k==="a"?L(w,D):M(D);if(typeof j==="string"){let H=Q(j);for(let z=0;z<H.length;z++)H[z][E](w,B,G)}else j[E](w,B,G)}function W(j,k){let w=k??document;return typeof w.querySelector==="function"?w.querySelector(j):null}function Q(j,k){let w=k??document;return typeof w.querySelectorAll==="function"?Array.from(w.querySelectorAll(j)):[]}
|
|
2
|
+
export{R as c,V as d,W as e,Q as f};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var J=0,G=0;function N(){return G}var v=[],x=0,D=!0,y=0,E=!1;function H(q){if(D){let j=q-y;y=q,G=j/16.666666666666668;for(let B=x-1;B>=0;B--){let A=v[B];if(!A)continue;A.sT||=q;let I=q-A.sT;A.cb(I)}}requestAnimationFrame(H)}function K(){if(document.hidden)D=!1;else{let q=performance.now()-y;y=performance.now();for(let j=x-1;j>=0;j--)v[j].sT+=q;D=!0}}function L(){if(E)return;if(E=!0,y=performance.now(),typeof document<"u")document.addEventListener("visibilitychange",K);requestAnimationFrame(H)}var M={add(q){L();let j=J++;return v.push({id:j,cb:q,sT:0}),x++,()=>M.remove(q)},remove(q){for(let j=x-1;j>=0;j--)if(v[j].cb===q)v.splice(j,1),x--}};
|
|
2
|
+
export{N as g,M as h};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{c as C,e as c,f as w}from"./chunk-0t3a7kj5.js";var I=1,x=2,K=4,_="inline-block",h="nowrap",f="baseline",u="0px",E="block",m="maskChar",g="maskWord",n="maskLine",o=/^\s+$/,l=/(\s+)/,t=",div,span,svg,img,br,hr,canvas,video,audio,iframe,input,textarea,select,button,picture,figure,",p={u:"underline",ins:"underline",s:"line-through",strike:"line-through",del:"line-through"};class a{dom;chars;words;lines;masks;_n;_k;_g;_j;_f=[];_o=new Map;_i=null;_h=0;_l=!1;_c="";_d;_e;_b;_a=null;constructor(z,F={}){let J;if(typeof z==="string"){let $=w(z);if($.length>1)J=$;else{let Q=$[0]??c(z);if(!Q)throw Error("SplitText: element not found");J=Q}}else J=z;if(Array.isArray(J)||typeof J==="object"&&J!==null&&"length"in J&&typeof J.item==="function"){let $=Array.from(J);if($.length===0)throw Error("SplitText: empty element array");this._a=$.map((Q)=>new a(Q,F)),this.dom=$,this.chars=this._a.flatMap((Q)=>Q.chars),this.words=this._a.flatMap((Q)=>Q.words),this.lines=this._a.flatMap((Q)=>Q.lines),this.masks={chars:this._a.flatMap((Q)=>Q.masks.chars),words:this._a.flatMap((Q)=>Q.masks.words),lines:this._a.flatMap((Q)=>Q.masks.lines)},this._n="",this._k=0,this._g=0,this._j="";return}let U=J;this._b=U,this.dom=U,this._n=U.innerHTML,this.chars=[],this.words=[],this.lines=[],this.masks={chars:[],words:[],lines:[]};let G=F.type?Array.isArray(F.type)?F.type:[F.type]:["chars","words","lines"],D=0;for(let $ of G)if($==="chars")D|=I;else if($==="words")D|=x;else if($==="lines")D|=K;this._k=D,this._j=F.tag??"span";let Z=0;if(F.mask===!0)Z=D;else if(Array.isArray(F.mask)){for(let $ of F.mask)if($==="chars")Z|=I;else if($==="words")Z|=x;else if($==="lines")Z|=K}if(this._g=Z,this._d=F.style,this._e=F.class,this._w(),(F.resize??!0)&&D&K)this._x()}_p(z,F){let J=document.createElement(this._j);if(z)J.dataset[z]="";if(J.style.display=_,F!==void 0)J.textContent=F;return J}_s(z,F){let J=document.createElement(this._j);if(J.dataset[F]="",J.style.overflow="hidden",J.style.display=z,this._d?.mask)Object.assign(J.style,this._d.mask);if(this._e?.mask)J.classList.add(this._e.mask);return J}_r(z,F,J,q,U){if(this._g&F){let G=this._s(J,q);return G.appendChild(z),U.push(G),G}return z}_m(z,F,J,q){if(J){let{outer:U,inner:G}=J();if(G.appendChild(F),q)this._o.set(q,U);z.appendChild(U)}else z.appendChild(F)}_q(z,F,J){if(z.style.textDecoration=F,J)z.style.textDecorationColor=J}_w(){this._l=!0;let z=this._b,F=C(z),J=F?parseFloat(F.letterSpacing)||0:0;if(this._c=F?F.textIndent:"",this._c&&this._c!==u)z.style.textIndent="0";let q=this._k,U=!!(q&I),G=!!(q&x),D=!!(q&K),Z=G||D,$=!!(this._g&I),Q=new Map,H=(M)=>{for(let N of Array.from(M.childNodes))if(N.nodeType===1){let O=N.tagName.toLowerCase();if(p[O]){let v=C(N)?.textDecorationColor;if(v)Q.set(N,v)}H(N)}};H(z);let Y=Array.from(z.childNodes),T=document.createDocumentFragment();z.replaceChildren();let k=(M,N,O,v)=>{if(M.nodeType===3){let X=(M.textContent??"").split(l);for(let j of X){if(!j)continue;if(o.test(j)){T.appendChild(document.createTextNode(j));continue}let V=Z?this._p(G?"word":""):null;if(V){if(V.style.whiteSpace=h,O&&!U)this._q(V,O,v)}if(U){let b=[...j],d=this.chars.length;for(let P=0;P<b.length;P++){let R=this._p("char",b[P]);if(J&&P<b.length-1)R.style.marginRight=`${J}px`;if(O)this._q(R,O,v);if(this.chars.push(R),this._d?.chars)Object.assign(R.style,this._d.chars);if(this._e?.chars)R.classList.add(this._e.chars);let y=$?this._r(R,I,_,m,this.masks.chars):R;if(V)V.appendChild(y)}if(!V){let P=N?N():null,R=P?P.inner:T;for(let y=d;y<this.chars.length;y++){let S=this.chars[y];R.appendChild($&&S.parentElement?.dataset?.[m]!==void 0?S.parentElement:S)}if(P)T.appendChild(P.outer)}}else if(V){if(V.textContent=j,J)V.style.letterSpacing=`${J}px`}if(V){if(G){if(this.words.push(V),this._d?.words)Object.assign(V.style,this._d.words);if(this._e?.words)V.classList.add(this._e.words)}this._f.push(V);let b=this._g&x?this._r(V,x,_,g,this.masks.words):V;this._m(T,b,N,V)}else if(!U)this._m(T,document.createTextNode(j),N)}return}if(M.nodeType!==1)return;let B=M,A=B.tagName.toLowerCase();if(t.includes(","+A+",")){let X=B.cloneNode(!0);if(O)this._q(X,O,v);let j=C(B)?.verticalAlign;if(j&&j!==f)X.style.verticalAlign=j;if(Z){let V=this._p(G?"word":"");if(V.style.whiteSpace=h,j&&j!==f)V.style.verticalAlign=j;if(V.appendChild(X),G){if(this.words.push(V),this._d?.words)Object.assign(V.style,this._d.words);if(this._e?.words)V.classList.add(this._e.words)}this._f.push(V);let b=this._g&x?this._r(V,x,_,g,this.masks.words):V;this._m(T,b,N,V)}else this._m(T,X,N);return}let r=()=>{let X=B.cloneNode(!1);if(N){let{outer:j,inner:V}=N();return V.appendChild(X),{outer:j,inner:X}}return{outer:X,inner:X}},L=p[A],i=L?O?`${O} ${L}`:L:O,s=L?Q.get(B)||v:v;for(let X of Array.from(B.childNodes))k(X,r,i,s)};for(let M of Y)k(M,null,"","");z.appendChild(T);let W=!!(this._c&&this._c!==u);if(D)this._y(W);else if(W){let M=G?this.words[0]:U?this.chars[0]:null;if(M)M.style.marginLeft=this._c,z.style.textIndent="0"}this._l=!1}_y(z){if(z&&this._f[0])this._f[0].style.marginLeft=this._c;this._t();let F=[];if(typeof document.fonts?.status==="string"&&document.fonts.status!=="loaded")F.push(document.fonts.ready);let J=this._b.querySelectorAll("img");for(let q=0;q<J.length;q++){let U=J[q];if(!U.complete)F.push(new Promise((G)=>{U.addEventListener("load",G,{once:!0}),U.addEventListener("error",G,{once:!0})}))}if(F.length){let q=this._b.offsetHeight;Promise.all(F).then(()=>{if(this._b.offsetHeight!==q)this.reflow()})}}_u(z){return this._o.get(z)??z}_t(){let z=this._f;if(!z.length)return;let F=z[0].offsetTop,J=F+z[0].offsetHeight,q=[],U=[];for(let $ of z){let Q=$.offsetTop,H=Q+$.offsetHeight;if(Q<J&&H>F)F=Math.min(F,Q),J=Math.max(J,H);else U.push(q),q=[],F=Q,J=H;q.push($)}if(q.length)U.push(q);let G=!!(this._c&&this._c!==u);if(G&&z[0])z[0].style.marginLeft="";let D=!!(this._g&K),Z=document.createDocumentFragment();for(let $=0;$<U.length;$++){let Q=U[$],H=document.createElement(this._j);if(H.dataset.line="",H.style.display=E,$===0&&G)H.style.paddingLeft=this._c;for(let Y=0;Y<Q.length;Y++){if(Y>0)H.appendChild(document.createTextNode(" "));H.appendChild(this._u(Q[Y]))}if(this.lines.push(H),this._d?.lines)Object.assign(H.style,this._d.lines);if(this._e?.lines)H.classList.add(this._e.lines);if(D){let Y=this._s(E,n);Y.appendChild(H),this.masks.lines.push(Y),Z.appendChild(Y)}else Z.appendChild(H)}this._b.replaceChildren(Z)}_x(){let z=this._b.offsetParent?this._b.offsetParent.offsetWidth:window.innerWidth,F=()=>{if(this._l)return;let q=this._b.offsetParent,U=q?q.offsetWidth:window.innerWidth;if(U===z)return;if(z=U,this._h)cancelAnimationFrame(this._h);this._h=requestAnimationFrame(()=>this.reflow())};this._i=new ResizeObserver(F);let J=this._b.offsetParent??this._b.parentElement;if(J)this._i.observe(J);else this._i.observe(this._b)}reflow(){if(this._a){this._a.forEach((F)=>F.reflow()),this._v();return}if(!(this._k&K)||this._l)return;let z=document.createDocumentFragment();for(let F=0;F<this._f.length;F++){if(F>0)z.appendChild(document.createTextNode(" "));z.appendChild(this._u(this._f[F]))}if(this._b.replaceChildren(z),this.lines.length=0,this.masks.lines.length=0,this._c&&this._c!==u&&this._f[0])this._f[0].style.marginLeft=this._c;this._t()}revert(){if(this._a){this._a.forEach((z)=>z.revert()),this._v();return}this.destroy(),this._b.innerHTML=this._n,this.chars.length=0,this.words.length=0,this._f.length=0,this._o.clear(),this.lines.length=0,this.masks.chars.length=0,this.masks.words.length=0,this.masks.lines.length=0}destroy(){if(this._a){this._a.forEach((z)=>z.destroy());return}if(this._h)cancelAnimationFrame(this._h),this._h=0;if(this._i)this._i.disconnect(),this._i=null}_v(){if(!this._a)return;this.chars.length=0,this.words.length=0,this.lines.length=0,this.masks.chars.length=0,this.masks.words.length=0,this.masks.lines.length=0;for(let z of this._a)this.chars.push(...z.chars),this.words.push(...z.words),this.lines.push(...z.lines),this.masks.chars.push(...z.masks.chars),this.masks.words.push(...z.masks.words),this.masks.lines.push(...z.masks.lines)}}
|
|
2
|
+
export{a};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{d as N,e as P}from"./chunk-0t3a7kj5.js";import{g as Z,h as Q}from"./chunk-qjsknche.js";function O(j,z,B){return Math.min(Math.max(j,z),B)}function $(j,z,B){return j+(z-j)*B}function X(j,z,B){let G=z-j;return G===0?0:O((B-j)/G,0,1)}function Y(j,z,B,G){let H=1-Math.exp(-B*G);return $(j,z,H)}var J=0.09;class y{_d;_f;_c;_x;_e;_y;_o=!1;_q=!1;_m=new Map;_a=0;_b=0;_k=0;_l=1;_p=!1;_L={current:0,target:0,velocity:0,direction:1,max:0,scrolling:!1};_g=!1;_w=0;_r=0;_E=0;_h=0;_s=null;_i=J;_j=null;_z;_A;_B;_t;constructor(j={}){let z=j.wrapper??window;this._d=typeof z==="string"?P(z)??window:z,this._f=this._d===window,this._c=(j.direction??"vertical")==="vertical",this._x=j.duration??0,this._e=j.ease??J,this._y=j.prevent??!this._f,this._z=this._H.bind(this),this._A=this._I.bind(this),this._B=this._J.bind(this),this._t=()=>this._K();let B=this._f?this._c?window.scrollY:window.scrollX:this._c?this._d.scrollTop:this._d.scrollLeft;this._a=B,this._b=B,this.start()}get scroll(){let j=this._L;return j.current=this._a,j.target=this._b,j.velocity=this._k,j.direction=this._l,j.max=this._u(),j.scrolling=this._p,j}_u(){if(this._f)return this._c?document.documentElement.scrollHeight-window.innerHeight:document.documentElement.scrollWidth-window.innerWidth;let j=this._d;return this._c?j.scrollHeight-j.clientHeight:j.scrollWidth-j.clientWidth}_v(j){return O(j,0,this._u())}on(j,z){let B=this._m.get(j);if(!B)B=new Set,this._m.set(j,B);B.add(z)}off(j,z){this._m.get(j)?.delete(z)}_D(j){let z=this._m.get(j);if(!z)return;let B=this.scroll;for(let G of z)G(B)}_n(){if(this._q)return;this._q=!0,Q.add(this._t)}_G(){if(!this._q)return;this._q=!1,Q.remove(this._t)}to(j,z={}){let B;if(typeof j==="number")B=j;else{let K=typeof j==="string"?P(j):j;if(!K)return;let M=K.getBoundingClientRect();if(this._f)B=(this._c?M.top:M.left)+this._a;else{let V=this._d.getBoundingClientRect();B=(this._c?M.top-V.top:M.left-V.left)+this._a}}if(B+=z.offset??0,B=this._v(B),z.immediate){this._a=B,this._b=B,this._g=!0,this._C(),this._D("scroll"),this._g=!1,z.onComplete?.();return}this._g=!0,this._n();let G=z.ease??this._e,H=z.duration??this._x;if(typeof G==="number")this._h=0,this._b=B,this._i=G,this._j=z.onComplete??null;else if(H>0)this._w=this._a,this._r=B,this._E=performance.now(),this._h=H,this._i=G,this._s=z.onComplete??null,this._j=null;else this._h=0,this._b=B,this._i=typeof this._e==="number"?this._e:J,this._j=z.onComplete??null}_F(j){let z=this._f?window:this._d;N(z,j,"wheel",this._z),N(z,j,"scroll",this._A,{passive:!0}),N(window,j,"keydown",this._B)}start(){if(this._o)return;this._o=!0,this._F("a")}stop(){if(!this._o)return;this._o=!1,this._F("r"),this._G()}destroy(){this.stop(),this._m.clear()}_H(j){if(j.preventDefault(),this._y)j.stopPropagation();let z=this._c?j.deltaY:j.deltaX;if(j.deltaMode===1)z*=0.833333;else if(j.deltaMode===2)z*=0.555556;this._b=this._v(this._b+z),this._h=0,this._i=typeof this._e==="number"?this._e:J,this._g=!0,this._n(),this._l=z>0?1:-1}_I(){if(this._g)return;let j=this._f?this._c?window.scrollY:window.scrollX:this._c?this._d.scrollTop:this._d.scrollLeft;this._a=j,this._b=j}_J(j){if(!this._f){let G=document.activeElement;if(G!==this._d&&!this._d.contains(G))return}let z=0,B=this._c?this._f?window.innerHeight:this._d.clientHeight:this._f?window.innerWidth:this._d.clientWidth;switch(j.key){case"ArrowDown":z=100;break;case"ArrowUp":z=-100;break;case"ArrowRight":if(!this._c)z=100;break;case"ArrowLeft":if(!this._c)z=-100;break;case" ":z=j.shiftKey?-B:B;break;case"PageDown":z=B;break;case"PageUp":z=-B;break;case"Home":this._b=0,this._h=0,this._i=typeof this._e==="number"?this._e:J,this._g=!0,this._n();return;case"End":this._b=this._u(),this._h=0,this._i=typeof this._e==="number"?this._e:J,this._g=!0,this._n();return;default:return}if(z)j.preventDefault(),this._b=this._v(this._b+z),this._h=0,this._i=typeof this._e==="number"?this._e:J,this._g=!0,this._n(),this._l=z>0?1:-1}_C(){if(this._f)if(this._c)window.scrollTo(0,this._a);else window.scrollTo(this._a,0);else{let j=this._d;if(this._c)j.scrollTop=this._a;else j.scrollLeft=this._a}}_K(){if(!this._o)return;let j=this._a,z=!1;if(this._g){if(this._h>0){let G=performance.now()-this._E,H=X(0,this._h,G),K=this._i(H);if(this._a=this._w+(this._r-this._w)*K,this._b=this._r,H>=1)this._a=this._r,this._h=0,z=!0}else{let G=this._i;if(this._a=Y(this._a,this._b,G,Z()),Math.abs(this._b-this._a)<0.5)this._a=this._b,z=!0}this._C()}this._k=this._a-j;let B=this._p;if(this._p=Math.abs(this._k)>0.01,this._k>0)this._l=1;else if(this._k<0)this._l=-1;if(this._p||B)this._D("scroll");if(z){if(this._g=!1,this._G(),this._s){let G=this._s;this._s=null,G()}if(this._j){let G=this._j;this._j=null,G()}}}}
|
|
2
|
+
export{y as b};
|
package/dist/dom/index.d.ts
CHANGED
|
@@ -1,2 +1,75 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Shorthand for `getComputedStyle`.
|
|
3
|
+
* @param el - The element to compute styles for.
|
|
4
|
+
* @param pseudo - Optional pseudo-element selector (e.g. `"::before"`).
|
|
5
|
+
* @returns The computed style declaration, or `null` when unavailable (e.g. SSR).
|
|
6
|
+
*/
|
|
2
7
|
export declare function gCS(el: Element, pseudo?: string | null): CSSStyleDeclaration | null;
|
|
8
|
+
/** A scope for DOM queries — defaults to `document` when `null` or `undefined`. */
|
|
9
|
+
type Scope = Document | Element | HTMLElement | null | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Shorthand DOM element getters.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* Get.i("app"); // document.getElementById("app")
|
|
16
|
+
* Get.c("card", wrapper); // wrapper.getElementsByClassName("card")
|
|
17
|
+
* Get.t("img"); // document.getElementsByTagName("img")
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare const Get: {
|
|
21
|
+
/**
|
|
22
|
+
* Get an element by its ID.
|
|
23
|
+
* @param id - The element ID (without `#`).
|
|
24
|
+
* @param scope - Optional parent scope.
|
|
25
|
+
*/
|
|
26
|
+
i: (id: string, scope?: Scope) => HTMLElement;
|
|
27
|
+
/**
|
|
28
|
+
* Get elements by class name.
|
|
29
|
+
* @param className - The class name to match.
|
|
30
|
+
* @param scope - Optional parent scope.
|
|
31
|
+
*/
|
|
32
|
+
c: (className: string, scope?: Scope) => HTMLElement[];
|
|
33
|
+
/**
|
|
34
|
+
* Get elements by tag name.
|
|
35
|
+
* @param tagName - The HTML tag to match.
|
|
36
|
+
* @param scope - Optional parent scope.
|
|
37
|
+
*/
|
|
38
|
+
t: (tagName: string, scope?: Scope) => HTMLElement[] | HTMLCollectionOf<HTMLElement>;
|
|
39
|
+
};
|
|
40
|
+
type ListenerScope = Element | HTMLElement | Document | Window;
|
|
41
|
+
/**
|
|
42
|
+
* Add or remove event listeners on one or more targets.
|
|
43
|
+
*
|
|
44
|
+
* Input events (`wheel`, `mouse*`, `touch*`, `pointer*`) default to
|
|
45
|
+
* `{ passive: false }` when no options are provided.
|
|
46
|
+
*
|
|
47
|
+
* @param target - A CSS selector, element, `document`, or `window`.
|
|
48
|
+
* @param action - `"a"` to add, `"r"` to remove.
|
|
49
|
+
* @param type - Event type (e.g. `"click"`, `"wheel"`).
|
|
50
|
+
* @param handler - The event handler.
|
|
51
|
+
* @param opts - Optional `AddEventListenerOptions`.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* Listen(window, "a", "scroll", onScroll, { passive: true });
|
|
56
|
+
* Listen(".card", "a", "click", onClick);
|
|
57
|
+
* Listen(el, "r", "wheel", onWheel);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function Listen(target: string | ListenerScope, action: "a" | "r", type: string, handler: EventListenerOrEventListenerObject | ((e: any) => void), opts?: AddEventListenerOptions): void;
|
|
61
|
+
/**
|
|
62
|
+
* `querySelector` shorthand — returns a single matching element or `null`.
|
|
63
|
+
* @param selector - A CSS selector string.
|
|
64
|
+
* @param scope - Optional parent scope (defaults to `document`).
|
|
65
|
+
* @returns The first matching element, or `null`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function qS<T extends HTMLElement = HTMLElement>(selector: string, scope?: Scope): T | null;
|
|
68
|
+
/**
|
|
69
|
+
* `querySelectorAll` shorthand — returns an array of matching elements.
|
|
70
|
+
* @param selector - A CSS selector string.
|
|
71
|
+
* @param scope - Optional parent scope (defaults to `document`).
|
|
72
|
+
* @returns An array of matching elements (empty if unavailable).
|
|
73
|
+
*/
|
|
74
|
+
export declare function qSA<T extends HTMLElement = HTMLElement>(selector: string, scope?: Scope): T[];
|
|
75
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
1
|
export { SplitText, type SplitTextOptions, type SplitType, } from "./split-text/index.ts";
|
|
2
|
+
export { SmoothScroll, type SmoothScrollOptions, type ScrollToOptions, type ScrollState, } from "./smooth-scroll/index.ts";
|
|
3
|
+
export { Ticker, getDeltaFrame, type TickerCallback } from "./ticker/index.ts";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as b}from"./chunk-
|
|
1
|
+
import{a as t}from"./chunk-se6yyp59.js";import{b as p}from"./chunk-xcfcqftx.js";import"./chunk-0t3a7kj5.js";import{g as e,h as o}from"./chunk-qjsknche.js";export{e as getDeltaFrame,o as Ticker,t as SplitText,p as SmoothScroll};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Round a number to a given precision.
|
|
3
|
+
* @param v - The value to round.
|
|
4
|
+
* @param r - Decimal places (defaults to 2 when omitted).
|
|
5
|
+
* @returns The rounded value.
|
|
6
|
+
*/
|
|
7
|
+
export declare const round: (v: number, r?: number) => number;
|
|
8
|
+
/**
|
|
9
|
+
* Clamp a value between a minimum and maximum.
|
|
10
|
+
* @param value - The value to clamp.
|
|
11
|
+
* @param min - Lower bound.
|
|
12
|
+
* @param max - Upper bound.
|
|
13
|
+
* @returns The clamped value.
|
|
14
|
+
*/
|
|
15
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* Linearly interpolate between two values.
|
|
18
|
+
* @param start - Start value (returned when `t = 0`).
|
|
19
|
+
* @param end - End value (returned when `t = 1`).
|
|
20
|
+
* @param t - Interpolation factor, typically in `[0, 1]`.
|
|
21
|
+
* @returns The interpolated value.
|
|
22
|
+
*/
|
|
23
|
+
export declare function lerp(start: number, end: number, t: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Damp a value towards a target using an exponential ease-out.
|
|
26
|
+
* @param current - Current value.
|
|
27
|
+
* @param target - Target value.
|
|
28
|
+
* @param lambda - Damping factor (higher values = faster).
|
|
29
|
+
* @param dt - Delta time (usually in seconds).
|
|
30
|
+
* @returns The dampened value.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Inverse linear interpolation — returns where `v` sits between `a` and `b` as a `[0, 1]` factor.
|
|
34
|
+
* @param a - Start value.
|
|
35
|
+
* @param b - End value.
|
|
36
|
+
* @param v - The value to map.
|
|
37
|
+
* @returns Clamped factor in `[0, 1]`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function iLerp(a: number, b: number, v: number): number;
|
|
40
|
+
export declare function damp(current: number, target: number, lambda: number, dt: number): number;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export interface SmoothScrollOptions {
|
|
2
|
+
wrapper?: Window | HTMLElement | string;
|
|
3
|
+
direction?: "vertical" | "horizontal";
|
|
4
|
+
duration?: number;
|
|
5
|
+
ease?: number | ((t: number) => number);
|
|
6
|
+
prevent?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ScrollToOptions {
|
|
9
|
+
offset?: number;
|
|
10
|
+
duration?: number;
|
|
11
|
+
ease?: number | ((t: number) => number);
|
|
12
|
+
immediate?: boolean;
|
|
13
|
+
onComplete?: () => void;
|
|
14
|
+
}
|
|
15
|
+
export interface ScrollState {
|
|
16
|
+
current: number;
|
|
17
|
+
target: number;
|
|
18
|
+
velocity: number;
|
|
19
|
+
direction: 1 | -1;
|
|
20
|
+
max: number;
|
|
21
|
+
scrolling: boolean;
|
|
22
|
+
}
|
|
23
|
+
type EventCallback = (scroll: ScrollState) => void;
|
|
24
|
+
export declare class SmoothScroll {
|
|
25
|
+
/** @internal */ _wrapper: Window | HTMLElement;
|
|
26
|
+
/** @internal */ _isW: boolean;
|
|
27
|
+
/** @internal */ _vertical: boolean;
|
|
28
|
+
/** @internal */ _duration: number;
|
|
29
|
+
/** @internal */ _ease: number | ((t: number) => number);
|
|
30
|
+
/** @internal */ _preventParent: boolean;
|
|
31
|
+
/** @internal */ _listening: boolean;
|
|
32
|
+
/** @internal */ _ticking: boolean;
|
|
33
|
+
/** @internal */ _listeners: Map<string, Set<EventCallback>>;
|
|
34
|
+
/** @internal */ _current: number;
|
|
35
|
+
/** @internal */ _target: number;
|
|
36
|
+
/** @internal */ _velocity: number;
|
|
37
|
+
/** @internal */ _dir: 1 | -1;
|
|
38
|
+
/** @internal */ _scrolling: boolean;
|
|
39
|
+
/** @internal */ _state: ScrollState;
|
|
40
|
+
/** @internal */ _driving: boolean;
|
|
41
|
+
/** @internal */ _aFrom: number;
|
|
42
|
+
/** @internal */ _aTo: number;
|
|
43
|
+
/** @internal */ _aStart: number;
|
|
44
|
+
/** @internal */ _aDur: number;
|
|
45
|
+
/** @internal */ _aCb: (() => void) | null;
|
|
46
|
+
/** @internal */ _aEase: number | ((t: number) => number);
|
|
47
|
+
/** @internal */ _aComplete: (() => void) | null;
|
|
48
|
+
/** @internal */ _onWheelBound: (e: WheelEvent) => void;
|
|
49
|
+
/** @internal */ _onScrollBound: () => void;
|
|
50
|
+
/** @internal */ _onKeyDownBound: (e: KeyboardEvent) => void;
|
|
51
|
+
/** @internal */ _tickBound: () => void;
|
|
52
|
+
constructor(options?: SmoothScrollOptions);
|
|
53
|
+
get scroll(): ScrollState;
|
|
54
|
+
/** @internal */
|
|
55
|
+
_max(): number;
|
|
56
|
+
/** @internal */
|
|
57
|
+
_clamp(v: number): number;
|
|
58
|
+
on(event: string, callback: EventCallback): void;
|
|
59
|
+
off(event: string, callback: EventCallback): void;
|
|
60
|
+
/** @internal */
|
|
61
|
+
_emit(event: string): void;
|
|
62
|
+
/** @internal — add ticker if not already ticking */
|
|
63
|
+
_startTick(): void;
|
|
64
|
+
/** @internal — remove ticker */
|
|
65
|
+
_stopTick(): void;
|
|
66
|
+
to(target: number | string | HTMLElement, options?: ScrollToOptions): void;
|
|
67
|
+
/** @internal */
|
|
68
|
+
_listen(action: "a" | "r"): void;
|
|
69
|
+
start(): void;
|
|
70
|
+
stop(): void;
|
|
71
|
+
destroy(): void;
|
|
72
|
+
/** @internal — wheel: intercepted for smooth damp */
|
|
73
|
+
_onWheel(e: WheelEvent): void;
|
|
74
|
+
/** @internal — native scroll event: sync when not driving (touch, programmatic, etc.) */
|
|
75
|
+
_onNativeScroll(): void;
|
|
76
|
+
/** @internal */
|
|
77
|
+
_onKeyDown(e: KeyboardEvent): void;
|
|
78
|
+
/** @internal */
|
|
79
|
+
_applyScroll(): void;
|
|
80
|
+
/** @internal */
|
|
81
|
+
_tick(): void;
|
|
82
|
+
}
|
|
83
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as a}from"../chunk-xcfcqftx.js";import"../chunk-0t3a7kj5.js";import"../chunk-qjsknche.js";export{a as SmoothScroll};
|
|
@@ -47,7 +47,7 @@ export declare class SplitText {
|
|
|
47
47
|
/** @internal */ _classes: SplitTextOptions["class"];
|
|
48
48
|
/** @internal */ _el: HTMLElement;
|
|
49
49
|
/** @internal */ _instances: SplitText[] | null;
|
|
50
|
-
constructor(
|
|
50
|
+
constructor(target: HTMLElement | HTMLElement[] | ArrayLike<HTMLElement> | string, options?: SplitTextOptions);
|
|
51
51
|
/** @internal — create a styled inline-block element. */
|
|
52
52
|
_makeEl(dataAttr: string, text?: string): HTMLElement;
|
|
53
53
|
/** @internal — create a mask wrapper element. */
|
package/dist/split-text/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a}from"../chunk-
|
|
1
|
+
import{a}from"../chunk-se6yyp59.js";import"../chunk-0t3a7kj5.js";export{a as SplitText};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type TickerCallback = (elapsed: number) => void;
|
|
2
|
+
/**
|
|
3
|
+
* Returns the normalized delta frame (1.0 = perfect 60fps frame).
|
|
4
|
+
*/
|
|
5
|
+
export declare function getDeltaFrame(): number;
|
|
6
|
+
export declare const Ticker: {
|
|
7
|
+
add(cb: TickerCallback): () => void;
|
|
8
|
+
remove(cb: TickerCallback): void;
|
|
9
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{g as a,h as b}from"../chunk-qjsknche.js";export{a as getDeltaFrame,b as Ticker};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# SmoothScroll
|
|
2
|
+
|
|
3
|
+
Smooth scroll with frame-rate independent damping. Intercepts wheel for smooth interpolation, uses native scroll for touch/mobile.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { SmoothScroll } from "situs-kit/smooth-scroll";
|
|
9
|
+
|
|
10
|
+
const scroll = new SmoothScroll();
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Options
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const scroll = new SmoothScroll({
|
|
17
|
+
wrapper: "#scroll-box", // window | HTMLElement | string (default: window)
|
|
18
|
+
direction: "vertical", // "vertical" | "horizontal"
|
|
19
|
+
duration: 800, // global to() duration in ms (0 = use damp)
|
|
20
|
+
ease: 0.09, // number → damp factor, function → tween ease (default: 0.09)
|
|
21
|
+
prevent: true, // stop wheel propagation (default: true for elements, false for window)
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Scroll State
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const { current, target, velocity, direction, max, scrolling } = scroll.scroll;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Note:** `scroll` returns a shared object that is mutated each frame. Destructure or copy the values if you need to compare across frames.
|
|
32
|
+
|
|
33
|
+
| Property | Type | Description |
|
|
34
|
+
|----------|------|-------------|
|
|
35
|
+
| `current` | `number` | Current interpolated position |
|
|
36
|
+
| `target` | `number` | Target position |
|
|
37
|
+
| `velocity` | `number` | Pixels moved since last frame |
|
|
38
|
+
| `direction` | `1 \| -1` | 1 = forward, -1 = backward |
|
|
39
|
+
| `max` | `number` | Maximum scroll value |
|
|
40
|
+
| `scrolling` | `boolean` | Whether currently scrolling |
|
|
41
|
+
|
|
42
|
+
## Methods
|
|
43
|
+
|
|
44
|
+
### `to(target, options?)`
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
scroll.to(500);
|
|
48
|
+
scroll.to("#section-2", { offset: -100 });
|
|
49
|
+
scroll.to(element, { duration: 1000 });
|
|
50
|
+
scroll.to(0, { immediate: true });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
| Option | Type | Description |
|
|
54
|
+
|--------|------|-------------|
|
|
55
|
+
| `offset` | `number` | Pixel offset added to target |
|
|
56
|
+
| `duration` | `number` | Animation duration in ms (defaults to constructor `duration`) |
|
|
57
|
+
| `ease` | `number \| (t: number) => number` | Number → damp factor, function → tween ease curve |
|
|
58
|
+
| `immediate` | `boolean` | Jump instantly |
|
|
59
|
+
| `onComplete` | `() => void` | Callback when scroll completes |
|
|
60
|
+
|
|
61
|
+
### `start()` / `stop()`
|
|
62
|
+
|
|
63
|
+
Resume or pause scroll listening and the animation loop.
|
|
64
|
+
|
|
65
|
+
### `destroy()`
|
|
66
|
+
|
|
67
|
+
Full cleanup — removes listeners, stops the ticker, clears events.
|
|
68
|
+
|
|
69
|
+
## Events
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
scroll.on("scroll", ({ current, target, velocity, direction, max }) => {});
|
|
73
|
+
scroll.off("scroll", callback);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Window vs Element Mode
|
|
77
|
+
|
|
78
|
+
Both modes use the same scroll-jacking approach: wheel is intercepted for smooth damping, touch and programmatic scrolls sync via native `scroll` events.
|
|
79
|
+
|
|
80
|
+
**Window** (default) — drives scroll via `window.scrollTo()`.
|
|
81
|
+
|
|
82
|
+
**Element** — pass a `wrapper` to enable. Drives scroll via `scrollTop`/`scrollLeft`. The wrapper element should have `overflow: auto` or `overflow: scroll`.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# SplitText
|
|
2
|
+
|
|
3
|
+
Split text into characters, words, and lines while preserving inline HTML elements like `<a>`, `<em>`, `<strong>`, and `<u>`.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { SplitText } from "situs-kit/split-text";
|
|
9
|
+
|
|
10
|
+
const split = new SplitText("#my-text", {
|
|
11
|
+
type: ["chars", "words", "lines"],
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
split.chars; // HTMLElement[]
|
|
15
|
+
split.words; // HTMLElement[]
|
|
16
|
+
split.lines; // HTMLElement[]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Constructor
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
new SplitText(
|
|
23
|
+
element: HTMLElement | HTMLElement[] | ArrayLike<HTMLElement> | string,
|
|
24
|
+
options?: SplitTextOptions
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `element` parameter accepts:
|
|
29
|
+
|
|
30
|
+
- A CSS selector string (e.g. `"#my-text"`, `".my-class"`)
|
|
31
|
+
- A single `HTMLElement`
|
|
32
|
+
- An array or array-like collection of `HTMLElement`s (creates sub-instances, aggregating results)
|
|
33
|
+
|
|
34
|
+
## Options
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
new SplitText(element, {
|
|
38
|
+
type: ["chars", "words", "lines"], // which levels to split
|
|
39
|
+
tag: "span", // wrapper element tag
|
|
40
|
+
mask: ["chars", "lines"], // which levels to mask
|
|
41
|
+
resize: true, // auto-reflow lines on resize
|
|
42
|
+
style: {
|
|
43
|
+
chars: { color: "red" },
|
|
44
|
+
words: {},
|
|
45
|
+
lines: {},
|
|
46
|
+
mask: {},
|
|
47
|
+
},
|
|
48
|
+
class: {
|
|
49
|
+
chars: "my-char",
|
|
50
|
+
words: "my-word",
|
|
51
|
+
lines: "my-line",
|
|
52
|
+
mask: "my-mask",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Option | Type | Default | Description |
|
|
58
|
+
|--------|------|---------|-------------|
|
|
59
|
+
| `type` | `SplitType \| SplitType[]` | `["chars", "words", "lines"]` | Split granularity |
|
|
60
|
+
| `tag` | `string` | `"span"` | Wrapper element tag |
|
|
61
|
+
| `mask` | `boolean \| SplitType[]` | `false` | Create overflow-hidden wrappers. `true` masks all requested types; array masks only specified types |
|
|
62
|
+
| `resize` | `boolean` | `true` | Auto-reflow lines on window resize (only applies when lines are split) |
|
|
63
|
+
| `style` | `object` | — | Inline styles per split type (`chars`, `words`, `lines`, `mask`) |
|
|
64
|
+
| `class` | `object` | — | CSS classes per split type (`chars`, `words`, `lines`, `mask`) |
|
|
65
|
+
|
|
66
|
+
### Types
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
type SplitType = "chars" | "words" | "lines"
|
|
70
|
+
|
|
71
|
+
type SplitTypeOption =
|
|
72
|
+
| SplitType
|
|
73
|
+
| ["chars"]
|
|
74
|
+
| ["words"]
|
|
75
|
+
| ["lines"]
|
|
76
|
+
| ["chars", "words"]
|
|
77
|
+
| ["chars", "lines"]
|
|
78
|
+
| ["words", "lines"]
|
|
79
|
+
| ["chars", "words", "lines"]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Properties
|
|
83
|
+
|
|
84
|
+
| Property | Type | Description |
|
|
85
|
+
|----------|------|-------------|
|
|
86
|
+
| `dom` | `HTMLElement \| HTMLElement[]` | The original element(s) |
|
|
87
|
+
| `chars` | `HTMLElement[]` | Character wrapper elements |
|
|
88
|
+
| `words` | `HTMLElement[]` | Word wrapper elements |
|
|
89
|
+
| `lines` | `HTMLElement[]` | Line wrapper elements |
|
|
90
|
+
| `masks` | `{ chars: HTMLElement[], words: HTMLElement[], lines: HTMLElement[] }` | Mask wrapper elements per split type |
|
|
91
|
+
|
|
92
|
+
## Methods
|
|
93
|
+
|
|
94
|
+
### `reflow()`
|
|
95
|
+
|
|
96
|
+
Recalculate line groupings without re-splitting characters or words. Called automatically on resize when `resize: true`. Does nothing if lines weren't requested in `type`.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
split.reflow();
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `revert()`
|
|
103
|
+
|
|
104
|
+
Restore the element to its original HTML. Clears all `chars`, `words`, `lines`, and `masks` arrays. Calls `destroy()` internally.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
split.revert();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `destroy()`
|
|
111
|
+
|
|
112
|
+
Remove resize observers and cancel pending animation frames. Called automatically by `revert()`. Safe to call multiple times.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
split.destroy();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Data Attributes
|
|
119
|
+
|
|
120
|
+
Split elements include data attributes for CSS targeting:
|
|
121
|
+
|
|
122
|
+
```css
|
|
123
|
+
[data-char] { display: inline-block; }
|
|
124
|
+
[data-word] { display: inline-block; }
|
|
125
|
+
[data-line] { display: block; }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Mask elements use:
|
|
129
|
+
|
|
130
|
+
- `data-maskChar`
|
|
131
|
+
- `data-maskWord`
|
|
132
|
+
- `data-maskLine`
|
|
133
|
+
|
|
134
|
+
## HTML Preservation
|
|
135
|
+
|
|
136
|
+
Inline elements like `<a>`, `<em>`, `<strong>`, `<u>`, `<i>`, `<b>`, `<ins>`, `<s>`, `<strike>`, and `<del>` are preserved and cloned around split elements.
|
|
137
|
+
|
|
138
|
+
Opaque elements like `<div>`, `<span>`, `<svg>`, `<img>`, `<br>`, `<canvas>`, `<video>`, `<audio>`, `<iframe>`, `<input>`, `<textarea>`, `<select>`, `<button>`, `<picture>`, and `<figure>` are treated as atomic units.
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<!-- Input -->
|
|
142
|
+
<p id="text">Hello <strong>world</strong></p>
|
|
143
|
+
|
|
144
|
+
<!-- After split, <strong> wraps are preserved -->
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Masking
|
|
148
|
+
|
|
149
|
+
Masks wrap split elements in `overflow: hidden` containers, useful for reveal animations. You can specify which split levels to mask:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// mask only lines
|
|
153
|
+
const split = new SplitText("#text", {
|
|
154
|
+
type: ["words", "lines"],
|
|
155
|
+
mask: ["lines"],
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
split.masks.lines; // HTMLElement[] - overflow:hidden wrappers
|
|
159
|
+
|
|
160
|
+
// mask all split types
|
|
161
|
+
const split2 = new SplitText("#text", {
|
|
162
|
+
type: ["chars", "words", "lines"],
|
|
163
|
+
mask: true,
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Multi-Element Support
|
|
168
|
+
|
|
169
|
+
When passing multiple elements (via selector, array, or array-like), SplitText creates sub-instances and aggregates all results into the top-level `chars`, `words`, `lines`, and `masks` arrays.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
const split = new SplitText(".my-text");
|
|
173
|
+
|
|
174
|
+
// chars/words/lines contain elements from all matched elements
|
|
175
|
+
split.chars; // HTMLElement[] from all .my-text elements
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Additional Behaviors
|
|
179
|
+
|
|
180
|
+
- **Letter spacing**: Automatically applied to character elements when the parent has `letter-spacing` set
|
|
181
|
+
- **Text indent**: Preserved on the first line
|
package/docs/ticker.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Ticker
|
|
2
|
+
|
|
3
|
+
Shared singleton RAF loop. Used internally by SmoothScroll.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { Ticker, getDeltaFrame } from "situs-kit/ticker";
|
|
9
|
+
|
|
10
|
+
const off = Ticker.add((elapsed) => {
|
|
11
|
+
const df = getDeltaFrame(); // 1.0 = 60fps frame
|
|
12
|
+
object.x += speed * df;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
off(); // unsubscribe
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## API
|
|
19
|
+
|
|
20
|
+
### `Ticker.add(callback): () => void`
|
|
21
|
+
|
|
22
|
+
Add a callback to the loop. Returns an unsubscribe function. The callback receives `elapsed` (ms since added).
|
|
23
|
+
|
|
24
|
+
### `Ticker.remove(callback): void`
|
|
25
|
+
|
|
26
|
+
Remove a callback by reference.
|
|
27
|
+
|
|
28
|
+
### `getDeltaFrame(): number`
|
|
29
|
+
|
|
30
|
+
Normalized delta frame. `1.0` at 60fps, `2.0` at 30fps, `0.5` at 120fps.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "situs-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A creative developer helper library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -15,10 +15,19 @@
|
|
|
15
15
|
"./split-text": {
|
|
16
16
|
"types": "./dist/split-text/index.d.ts",
|
|
17
17
|
"import": "./dist/split-text/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./smooth-scroll": {
|
|
20
|
+
"types": "./dist/smooth-scroll/index.d.ts",
|
|
21
|
+
"import": "./dist/smooth-scroll/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./ticker": {
|
|
24
|
+
"types": "./dist/ticker/index.d.ts",
|
|
25
|
+
"import": "./dist/ticker/index.js"
|
|
18
26
|
}
|
|
19
27
|
},
|
|
20
28
|
"files": [
|
|
21
|
-
"dist"
|
|
29
|
+
"dist",
|
|
30
|
+
"docs"
|
|
22
31
|
],
|
|
23
32
|
"scripts": {
|
|
24
33
|
"build": "bun build.ts && bunx tsc -p tsconfig.build.json",
|
package/dist/chunk-8pnv9yxr.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
function C(q,z){return typeof globalThis.getComputedStyle==="function"?globalThis.getComputedStyle(q,z??null):null}var B=1,P=2,y=4,I="inline-block",h="nowrap",f="baseline",L="0px",g="block",E="maskChar",p="maskWord",s="maskLine",o=/^\s+$/,n=/(\s+)/,d=",div,span,svg,img,br,hr,canvas,video,audio,iframe,input,textarea,select,button,picture,figure,",r={u:"underline",ins:"underline",s:"line-through",strike:"line-through",del:"line-through"};class a{dom;chars;words;lines;masks;_n;_k;_g;_j;_f=[];_o=new Map;_i=null;_h=0;_l=!1;_c="";_d;_e;_b;_a=null;constructor(q,z={}){if(typeof q==="string"){let U=document.querySelectorAll?document.querySelectorAll(q):null;if(U&&U.length>1)q=Array.from(U);else{let V=U?U[0]:document.querySelector(q);if(!V)throw Error("SplitText: element not found");q=V}}if(Array.isArray(q)||typeof q==="object"&&q!==null&&"length"in q&&typeof q.item==="function"){let U=Array.from(q);if(U.length===0)throw Error("SplitText: empty element array");this._a=U.map((V)=>new a(V,z)),this.dom=U,this.chars=this._a.flatMap((V)=>V.chars),this.words=this._a.flatMap((V)=>V.words),this.lines=this._a.flatMap((V)=>V.lines),this.masks={chars:this._a.flatMap((V)=>V.masks.chars),words:this._a.flatMap((V)=>V.masks.words),lines:this._a.flatMap((V)=>V.masks.lines)},this._n="",this._k=0,this._g=0,this._j="";return}let $=q;this._b=$,this.dom=$,this._n=$.innerHTML,this.chars=[],this.words=[],this.lines=[],this.masks={chars:[],words:[],lines:[]};let G=z.type?Array.isArray(z.type)?z.type:[z.type]:["chars","words","lines"],Q=0;for(let U of G)if(U==="chars")Q|=B;else if(U==="words")Q|=P;else if(U==="lines")Q|=y;this._k=Q,this._j=z.tag??"span";let T=0;if(z.mask===!0)T=Q;else if(Array.isArray(z.mask)){for(let U of z.mask)if(U==="chars")T|=B;else if(U==="words")T|=P;else if(U==="lines")T|=y}if(this._g=T,this._d=z.style,this._e=z.class,this._w(),(z.resize??!0)&&Q&y)this._x()}_p(q,z){let F=document.createElement(this._j);if(q)F.dataset[q]="";if(F.style.display=I,z!==void 0)F.textContent=z;return F}_s(q,z){let F=document.createElement(this._j);if(F.dataset[z]="",F.style.overflow="hidden",F.style.display=q,this._d?.mask)Object.assign(F.style,this._d.mask);if(this._e?.mask)F.classList.add(this._e.mask);return F}_r(q,z,F,$,G){if(this._g&z){let Q=this._s(F,$);return Q.appendChild(q),G.push(Q),Q}return q}_m(q,z,F,$){if(F){let{outer:G,inner:Q}=F();if(Q.appendChild(z),$)this._o.set($,G);q.appendChild(G)}else q.appendChild(z)}_q(q,z,F){if(q.style.textDecoration=z,F)q.style.textDecorationColor=F}_w(){this._l=!0;let q=this._b,z=C(q),F=z?parseFloat(z.letterSpacing)||0:0;if(this._c=z?z.textIndent:"",this._c&&this._c!==L)q.style.textIndent="0";let $=this._k,G=!!($&B),Q=!!($&P),T=!!($&y),U=Q||T,V=!!(this._g&B),X=new Map,H=(N)=>{for(let u of Array.from(N.childNodes))if(u.nodeType===1){let R=u.tagName.toLowerCase();if(r[R]){let Y=C(u)?.textDecorationColor;if(Y)X.set(u,Y)}H(u)}};H(q);let Z=Array.from(q.childNodes),O=document.createDocumentFragment();q.replaceChildren();let k=(N,u,R,Y)=>{if(N.nodeType===3){let D=(N.textContent??"").split(n);for(let j of D){if(!j)continue;if(o.test(j)){O.appendChild(document.createTextNode(j));continue}let J=U?this._p(Q?"word":""):null;if(J){if(J.style.whiteSpace=h,R&&!G)this._q(J,R,Y)}if(G){let v=[...j],i=this.chars.length;for(let b=0;b<v.length;b++){let M=this._p("char",v[b]);if(F&&b<v.length-1)M.style.marginRight=`${F}px`;if(R)this._q(M,R,Y);if(this.chars.push(M),this._d?.chars)Object.assign(M.style,this._d.chars);if(this._e?.chars)M.classList.add(this._e.chars);let S=V?this._r(M,B,I,E,this.masks.chars):M;if(J)J.appendChild(S)}if(!J){let b=u?u():null,M=b?b.inner:O;for(let S=i;S<this.chars.length;S++){let _=this.chars[S];M.appendChild(V&&_.parentElement?.dataset?.[E]!==void 0?_.parentElement:_)}if(b)O.appendChild(b.outer)}}else if(J){if(J.textContent=j,F)J.style.letterSpacing=`${F}px`}if(J){if(Q){if(this.words.push(J),this._d?.words)Object.assign(J.style,this._d.words);if(this._e?.words)J.classList.add(this._e.words)}this._f.push(J);let v=this._g&P?this._r(J,P,I,p,this.masks.words):J;this._m(O,v,u,J)}else if(!G)this._m(O,document.createTextNode(j),u)}return}if(N.nodeType!==1)return;let x=N,W=x.tagName.toLowerCase();if(d.includes(","+W+",")){let D=x.cloneNode(!0);if(R)this._q(D,R,Y);let j=C(x)?.verticalAlign;if(j&&j!==f)D.style.verticalAlign=j;if(U){let J=this._p(Q?"word":"");if(J.style.whiteSpace=h,j&&j!==f)J.style.verticalAlign=j;if(J.appendChild(D),Q){if(this.words.push(J),this._d?.words)Object.assign(J.style,this._d.words);if(this._e?.words)J.classList.add(this._e.words)}this._f.push(J);let v=this._g&P?this._r(J,P,I,p,this.masks.words):J;this._m(O,v,u,J)}else this._m(O,D,u);return}let m=()=>{let D=x.cloneNode(!1);if(u){let{outer:j,inner:J}=u();return J.appendChild(D),{outer:j,inner:D}}return{outer:D,inner:D}},K=r[W],c=K?R?`${R} ${K}`:K:R,w=K?X.get(x)||Y:Y;for(let D of Array.from(x.childNodes))k(D,m,c,w)};for(let N of Z)k(N,null,"","");q.appendChild(O);let A=!!(this._c&&this._c!==L);if(T)this._y(A);else if(A){let N=Q?this.words[0]:G?this.chars[0]:null;if(N)N.style.marginLeft=this._c,q.style.textIndent="0"}this._l=!1}_y(q){if(q&&this._f[0])this._f[0].style.marginLeft=this._c;this._t();let z=[];if(typeof document.fonts?.status==="string"&&document.fonts.status!=="loaded")z.push(document.fonts.ready);let F=this._b.querySelectorAll("img");for(let $=0;$<F.length;$++){let G=F[$];if(!G.complete)z.push(new Promise((Q)=>{G.addEventListener("load",Q,{once:!0}),G.addEventListener("error",Q,{once:!0})}))}if(z.length){let $=this._b.offsetHeight;Promise.all(z).then(()=>{if(this._b.offsetHeight!==$)this.reflow()})}}_u(q){return this._o.get(q)??q}_t(){let q=this._f;if(!q.length)return;let z=q[0].offsetTop,F=z+q[0].offsetHeight,$=[],G=[];for(let V of q){let X=V.offsetTop,H=X+V.offsetHeight;if(X<F&&H>z)z=Math.min(z,X),F=Math.max(F,H);else G.push($),$=[],z=X,F=H;$.push(V)}if($.length)G.push($);let Q=!!(this._c&&this._c!==L);if(Q&&q[0])q[0].style.marginLeft="";let T=!!(this._g&y),U=document.createDocumentFragment();for(let V=0;V<G.length;V++){let X=G[V],H=document.createElement(this._j);if(H.dataset.line="",H.style.display=g,V===0&&Q)H.style.paddingLeft=this._c;for(let Z=0;Z<X.length;Z++){if(Z>0)H.appendChild(document.createTextNode(" "));H.appendChild(this._u(X[Z]))}if(this.lines.push(H),this._d?.lines)Object.assign(H.style,this._d.lines);if(this._e?.lines)H.classList.add(this._e.lines);if(T){let Z=this._s(g,s);Z.appendChild(H),this.masks.lines.push(Z),U.appendChild(Z)}else U.appendChild(H)}this._b.replaceChildren(U)}_x(){let q=this._b.offsetParent?this._b.offsetParent.offsetWidth:window.innerWidth,z=()=>{if(this._l)return;let $=this._b.offsetParent,G=$?$.offsetWidth:window.innerWidth;if(G===q)return;if(q=G,this._h)cancelAnimationFrame(this._h);this._h=requestAnimationFrame(()=>this.reflow())};this._i=new ResizeObserver(z);let F=this._b.offsetParent??this._b.parentElement;if(F)this._i.observe(F);else this._i.observe(this._b)}reflow(){if(this._a){this._a.forEach((z)=>z.reflow()),this._v();return}if(!(this._k&y)||this._l)return;let q=document.createDocumentFragment();for(let z=0;z<this._f.length;z++){if(z>0)q.appendChild(document.createTextNode(" "));q.appendChild(this._u(this._f[z]))}if(this._b.replaceChildren(q),this.lines.length=0,this.masks.lines.length=0,this._c&&this._c!==L&&this._f[0])this._f[0].style.marginLeft=this._c;this._t()}revert(){if(this._a){this._a.forEach((q)=>q.revert()),this._v();return}this.destroy(),this._b.innerHTML=this._n,this.chars.length=0,this.words.length=0,this._f.length=0,this._o.clear(),this.lines.length=0,this.masks.chars.length=0,this.masks.words.length=0,this.masks.lines.length=0}destroy(){if(this._a){this._a.forEach((q)=>q.destroy());return}if(this._h)cancelAnimationFrame(this._h),this._h=0;if(this._i)this._i.disconnect(),this._i=null}_v(){if(!this._a)return;this.chars.length=0,this.words.length=0,this.lines.length=0,this.masks.chars.length=0,this.masks.words.length=0,this.masks.lines.length=0;for(let q of this._a)this.chars.push(...q.chars),this.words.push(...q.words),this.lines.push(...q.lines),this.masks.chars.push(...q.masks.chars),this.masks.words.push(...q.masks.words),this.masks.lines.push(...q.masks.lines)}}
|
|
2
|
-
export{a};
|