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 CHANGED
@@ -8,125 +8,11 @@ A zero-dependency creative developer toolkit
8
8
  npm install situs-kit
9
9
  ```
10
10
 
11
- ## SplitText
11
+ ## Modules
12
12
 
13
- Split text into characters, words, and lines while preserving inline HTML elements like `<a>`, `<em>`, `<strong>`, and `<u>`.
14
-
15
- ### Basic Usage
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};
@@ -1,2 +1,75 @@
1
- /** Shorthand for `getComputedStyle`. Returns `null` when unavailable (e.g. SSR). */
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-8pnv9yxr.js";export{b as SplitText};
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(element: HTMLElement | HTMLElement[] | ArrayLike<HTMLElement> | string, options?: SplitTextOptions);
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. */
@@ -1 +1 @@
1
- import{a}from"../chunk-8pnv9yxr.js";export{a as SplitText};
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.1.8",
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",
@@ -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};