@alexapvl/drwr 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # drwr
2
+
3
+ A zero-dependency, framework-agnostic bottom sheet / drawer component. Vanilla JS + CSS. Works in any project.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install drwr
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```js
14
+ import { Sheet } from "drwr";
15
+ import "drwr/style.css";
16
+
17
+ const sheet = new Sheet(document.getElementById("my-sheet"), {
18
+ snapPoints: [0, 0.5, 1],
19
+ defaultSnap: 0,
20
+ overlay: true,
21
+ dragHandle: true,
22
+ });
23
+
24
+ sheet.open(); // open to highest snap
25
+ sheet.open(0.5); // open to specific snap
26
+ sheet.snapTo(0.5);
27
+ sheet.close();
28
+ sheet.refresh(); // re-measure after dynamic content change
29
+ sheet.destroy();
30
+ ```
31
+
32
+ ## Options
33
+
34
+ | Option | Type | Default | Description |
35
+ |--------|------|---------|-------------|
36
+ | `snapPoints` | `number[]` | `[0, 1]` | Normalized snap points from 0 to 1 (0 = closed, 1 = max height). Sanitized on input. |
37
+ | `defaultSnap` | `number` | `0` | Initial snap point. Non-zero values open with full state (scroll lock, focus trap, etc). |
38
+ | `overlay` | `boolean` | `true` | Show backdrop overlay |
39
+ | `dragHandle` | `boolean` | `true` | Render a drag handle bar |
40
+ | `damping` | `number` | `30` | Spring damping coefficient |
41
+ | `stiffness` | `number` | `200` | Spring stiffness coefficient |
42
+ | `closeThreshold` | `number` | `0.2` | Fraction of lowest snap below which the sheet closes on release |
43
+ | `topPadding` | `number` | `40` | Space left above the sheet at max height, in px |
44
+ | `width` | `number` | `60` (desktop) / `100` (mobile) | Width as % of viewport |
45
+ | `align` | `"center" \| "left" \| "right"` | `"center"` | Horizontal alignment |
46
+ | `sidePadding` | `number` | `0` | Minimum side padding in px |
47
+ | `maxWidth` | `number` | `0` | Max width in px (0 = no max) |
48
+ | `dragMode` | `"handle" \| "sheet" \| "none"` | `"sheet"` | Which part initiates drag |
49
+ | `ariaLabel` | `string` | `"Sheet"` | Accessible label for the dialog |
50
+ | `ariaLabelledBy` | `string` | — | ID of labelling element (overrides `ariaLabel`) |
51
+ | `closeOnEscape` | `boolean` | `true` | Close on Escape key |
52
+ | `closeOnOverlayClick` | `boolean` | `true` | Close on overlay click |
53
+ | `trapFocus` | `boolean` | `true` | Trap focus inside the sheet |
54
+ | `modal` | `boolean` | `true` | Set `aria-modal` |
55
+ | `reducedMotion` | `boolean` | `true` | Skip spring animation when user prefers reduced motion |
56
+ | `onOpen` | `() => void` | — | Called when sheet opens |
57
+ | `onClose` | `() => void` | — | Called when sheet closes |
58
+ | `onSnap` | `(point: number) => void` | — | Called when sheet snaps to a point |
59
+
60
+ ## Methods
61
+
62
+ | Method | Description |
63
+ |--------|-------------|
64
+ | `open(point?)` | Open to the given snap (or highest). |
65
+ | `close()` | Close the sheet. |
66
+ | `snapTo(point)` | Animate to a specific snap point. |
67
+ | `refresh()` | Re-measure content height after dynamic changes. |
68
+ | `setSnapPoints(points)` | Replace snap points (sanitized). |
69
+ | `setLayout({ width?, align?, sidePadding? })` | Update layout at runtime. |
70
+ | `setOptions(opts)` | Merge partial options at runtime. |
71
+ | `destroy()` | Tear down the sheet and restore DOM. |
72
+
73
+ ## Theming
74
+
75
+ All visual tokens are exposed as CSS custom properties:
76
+
77
+ ```css
78
+ :root {
79
+ --drwr-bg: #fff;
80
+ --drwr-radius: 16px;
81
+ --drwr-handle-width: 32px;
82
+ --drwr-handle-height: 4px;
83
+ --drwr-handle-color: rgba(0, 0, 0, 0.25);
84
+ --drwr-overlay-color: rgba(0, 0, 0, 0.4);
85
+ --drwr-shadow: 0 -1px 0 rgba(0, 0, 0, 0.04), 0 -8px 32px rgba(0, 0, 0, 0.12), 0 -24px 60px rgba(0, 0, 0, 0.06);
86
+ --drwr-border: 1px solid rgba(0, 0, 0, 0.06);
87
+ --drwr-z-index: 9999;
88
+ }
89
+ ```
90
+
91
+ ## License
92
+
93
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var S=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var D=(i,e)=>{for(var t in e)S(i,t,{get:e[t],enumerable:!0})},Y=(i,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of k(e))!F.call(i,r)&&r!==t&&S(i,r,{get:()=>e[r],enumerable:!(s=A(e,r))||s.enumerable});return i};var B=i=>Y(S({},"__esModule",{value:!0}),i);var N={};D(N,{Sheet:()=>y});module.exports=B(N);var L={damping:30,stiffness:200,mass:1,restThreshold:.5};function R(i,e,t,s,r){let{stiffness:n,damping:a,mass:d}=r,o=i-e,c=-n*o,u=-a*t,l=(c+u)/d,h=t+l*s;return{position:i+h*s,velocity:h}}function z(i,e,t,s=L.restThreshold){return Math.abs(i-e)<s&&Math.abs(t)<s}function T(i,e,t,s,r,n){let a={...L,...s},d=i,o=t,c=0,u=0;function l(h){if(c===0){c=h,u=requestAnimationFrame(l);return}let p=Math.min((h-c)/1e3,.064);c=h;let m=R(d,e,o,p,a);if(d=m.position,o=m.velocity,r(d),z(d,e,o,a.restThreshold)){r(e),n();return}u=requestAnimationFrame(l)}return u=requestAnimationFrame(l),()=>cancelAnimationFrame(u)}var g=class{constructor(e=8){this.samples=[];this.maxSamples=e}add(e){this.samples.push({time:performance.now(),value:e}),this.samples.length>this.maxSamples&&this.samples.shift()}getVelocity(){if(this.samples.length<2)return 0;let e=this.samples[this.samples.length-1],t=this.samples[0];for(let r=this.samples.length-2;r>=0;r--)if(e.time-this.samples[r].time>=30){t=this.samples[r];break}let s=(e.time-t.time)/1e3;return s===0?0:(e.value-t.value)/s}reset(){this.samples=[]}};function P(i,e,t,s=400,r=1200){if(t.length===0)return i;if(t.length===1)return t[0];let n=[...t].sort((o,c)=>o-c);if(Math.abs(e)>r)return e>0?n[n.length-1]:n[0];if(Math.abs(e)>s)if(e>0){for(let o of n)if(o>i+2)return o;return n[n.length-1]}else{for(let o=n.length-1;o>=0;o--)if(n[o]<i-2)return n[o];return n[0]}let a=n[0],d=Math.abs(i-n[0]);for(let o=1;o<n.length;o++){let c=Math.abs(i-n[o]);c<d&&(a=n[o],d=c)}return a}var x=6,G=3;function K(i,e){let t=i;for(;t&&t!==e;){if(t.scrollHeight>t.clientHeight){let r=getComputedStyle(t).overflowY;if(r==="auto"||r==="scroll")return t}t=t.parentElement}return null}function H(i,e,t,s){let r=new g,n={active:!1,dragging:!1,startY:0,currentY:0,directionLocked:!1,lockedAxis:null,startX:0,currentX:0,scrolling:!1,scrollTarget:null};function a(l){let h=e?.contains(l.target);if(t&&!h)return;let p=h?null:K(l.target,i);n.active=!0,n.dragging=!1,n.startY=l.clientY,n.currentY=l.clientY,n.startX=l.clientX,n.currentX=l.clientX,n.directionLocked=!1,n.lockedAxis=null,n.scrolling=!1,n.scrollTarget=p,r.reset(),r.add(l.clientY),i.setPointerCapture(l.pointerId)}function d(l){if(!n.active||n.scrolling)return;n.currentY=l.clientY,n.currentX=l.clientX;let h=Math.abs(n.currentX-n.startX),p=Math.abs(n.currentY-n.startY),m=n.currentY-n.startY;if(!(!n.directionLocked&&((h>x||p>x)&&(n.directionLocked=!0,n.lockedAxis=p>=h?"y":"x"),p<G))&&n.lockedAxis!=="x"){if(n.scrollTarget&&!n.dragging){let f=n.scrollTarget,C=f.scrollTop<=0,O=f.scrollTop+f.clientHeight>=f.scrollHeight-1,w=m>0;if(!(w&&C)){if(!(!w&&O)){n.scrolling=!0;return}}}n.dragging||(n.dragging=!0,s.onStart(n.startY)),l.preventDefault(),r.add(l.clientY),s.onMove(n.currentY,m)}}function o(l){if(!n.active||(n.active=!1,i.releasePointerCapture(l.pointerId),!n.dragging))return;n.dragging=!1;let h=r.getVelocity();s.onEnd(h)}function c(l){n.active&&(n.active=!1,i.releasePointerCapture(l.pointerId),n.dragging&&(n.dragging=!1,s.onEnd(0)))}i.addEventListener("pointerdown",a),i.addEventListener("pointermove",d),i.addEventListener("pointerup",o),i.addEventListener("pointercancel",c);let u=l=>{n.active&&l.preventDefault()};return i.addEventListener("contextmenu",u),()=>{i.removeEventListener("pointerdown",a),i.removeEventListener("pointermove",d),i.removeEventListener("pointerup",o),i.removeEventListener("pointercancel",c),i.removeEventListener("contextmenu",u)}}var W=["a[href]","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", ");function v(i){let e=null;function t(){return Array.from(i.querySelectorAll(W))}function s(a){if(a.key!=="Tab")return;let d=t();if(d.length===0){a.preventDefault();return}let o=d[0],c=d[d.length-1];a.shiftKey?(document.activeElement===o||!i.contains(document.activeElement))&&(a.preventDefault(),c.focus()):(document.activeElement===c||!i.contains(document.activeElement))&&(a.preventDefault(),o.focus())}function r(){e=document.activeElement,i.addEventListener("keydown",s),requestAnimationFrame(()=>{let a=t();a.length>0?a[0].focus():i.focus()})}function n(){i.removeEventListener("keydown",s),e?.focus(),e=null}return r(),n}function E(i,e,t={}){i.setAttribute("role","dialog"),i.setAttribute("aria-modal",t.modal!==!1?"true":"false"),t.ariaLabelledBy?(i.setAttribute("aria-labelledby",t.ariaLabelledBy),i.removeAttribute("aria-label")):(i.setAttribute("aria-label",t.ariaLabel||"Sheet"),i.removeAttribute("aria-labelledby")),e&&e.setAttribute("aria-hidden","true")}function M(i,e){i.removeAttribute("role"),i.removeAttribute("aria-modal"),e&&e.removeAttribute("aria-hidden")}function b(){let i=document.body,e=document.documentElement,t=i.style.overflow,s=e.style.overflow;e.style.overflow="hidden",i.style.overflow="hidden";let r=n=>{let a=n.target;for(;a;){if(a.classList?.contains("drwr-content"))return;a=a.parentElement}n.preventDefault()};return document.addEventListener("touchmove",r,{passive:!1}),()=>{i.style.overflow=t,e.style.overflow=s,document.removeEventListener("touchmove",r)}}function X(i){return 8*(Math.log(i+1)-2)}var y=class i{constructor(e,t={}){this.overlayEl=null;this.handleEl=null;this.state="closed";this.currentSnap=0;this.currentH=0;this.dragStartH=0;this.cancelAnimation=null;this.cleanupGestures=null;this.cleanupFocusTrap=null;this.unlockScroll=null;this.onPageShow=null;this.onResize=null;this.naturalContentH=0;this.destroyed=!1;this.onKeyDown=e=>{e.key==="Escape"&&this.state!=="closed"&&this.options.closeOnEscape&&this.close()};this.container=e;let s=i.sanitizeSnapPoints(t.snapPoints??[0,1]);this.options={snapPoints:s,defaultSnap:t.defaultSnap??0,overlay:t.overlay??!0,dragHandle:t.dragHandle??!0,damping:t.damping??30,stiffness:t.stiffness??200,mass:t.mass??1,closeThreshold:t.closeThreshold??.2,topPadding:t.topPadding??40,width:t.width??(window.innerWidth>=768?60:100),align:t.align??"center",sidePadding:t.sidePadding??0,maxWidth:t.maxWidth??0,dragMode:t.dragMode??"sheet",ariaLabel:t.ariaLabel??"Sheet",ariaLabelledBy:t.ariaLabelledBy??"",closeOnEscape:t.closeOnEscape??!0,closeOnOverlayClick:t.closeOnOverlayClick??!0,trapFocus:t.trapFocus??!0,modal:t.modal??!0,reducedMotion:t.reducedMotion??!0,onOpen:t.onOpen??(()=>{}),onClose:t.onClose??(()=>{}),onSnap:t.onSnap??(()=>{})},this.buildDOM(),this.container.removeAttribute("hidden"),this.naturalContentH=this.getContentHeight(),this.bindGestures();let r=this.options.defaultSnap;r>0&&this.options.snapPoints.includes(r)?(this.currentH=this.snapToPixels(r),this.render(this.currentH),this.state="open",this.currentSnap=r,document.addEventListener("keydown",this.onKeyDown),this.unlockScroll||(this.unlockScroll=b()),this.options.trapFocus&&!this.cleanupFocusTrap&&(this.cleanupFocusTrap=v(this.wrapperEl)),queueMicrotask(()=>{this.destroyed||this.options.onOpen()})):(this.currentH=0,this.render(0)),this.onPageShow=n=>{n.persisted&&this.state!=="closed"&&this.resetToClosedState()},window.addEventListener("pageshow",this.onPageShow),this.onResize=()=>{this.applyLayout(),this.naturalContentH=this.getContentHeight(),this.state!=="closed"&&this.currentSnap>0&&(this.currentH=this.snapToPixels(this.currentSnap),this.render(this.currentH))},window.addEventListener("resize",this.onResize)}static sanitizeSnapPoints(e){let t=e.filter(r=>Number.isFinite(r)&&r>=0&&r<=1),s=[...new Set(t)].sort((r,n)=>r-n);return s.length===0?[0,1]:(s.includes(0)||s.unshift(0),s)}prefersReducedMotion(){return this.options.reducedMotion&&typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches}getContentHeight(){return this.contentEl.scrollHeight+(this.handleEl?.offsetHeight??0)}maxSheetH(){return window.innerHeight-this.options.topPadding}snapToPixels(e){if(e<=0)return 0;let t=this.maxSheetH();return Math.min(e,1)*t}snapPixels(){return this.options.snapPoints.map(e=>this.snapToPixels(e)).sort((e,t)=>e-t)}pixelsToSnap(e){let t=this.options.snapPoints,s=t[0],r=Math.abs(e-this.snapToPixels(t[0]));for(let n=1;n<t.length;n++){let a=Math.abs(e-this.snapToPixels(t[n]));a<r&&(s=t[n],r=a)}return s}applyLayout(){let{width:e,align:t,sidePadding:s,maxWidth:r}=this.options,n=this.wrapperEl,a=window.innerWidth,d=Math.max(10,Math.min(100,e)),o=Math.max(0,Math.min(s,a/2-10)),c=Math.max(20,Math.min(a*(d/100),a-o*2));r>0&&(c=Math.min(c,r));let u;t==="left"?u=o:t==="right"?u=a-c-o:u=(a-c)/2,n.style.left=`${u}px`,n.style.right="auto",n.style.width=`${c}px`,n.style.margin="0"}buildDOM(){for(this.wrapperEl=document.createElement("div"),this.wrapperEl.className="drwr-sheet",this.wrapperEl.setAttribute("tabindex","-1"),this.options.dragHandle&&(this.handleEl=document.createElement("div"),this.handleEl.className="drwr-handle",this.wrapperEl.appendChild(this.handleEl)),this.contentEl=document.createElement("div"),this.contentEl.className="drwr-content";this.container.firstChild;)this.contentEl.appendChild(this.container.firstChild);this.wrapperEl.appendChild(this.contentEl),this.container.appendChild(this.wrapperEl),this.options.overlay&&(this.overlayEl=document.createElement("div"),this.overlayEl.className="drwr-overlay",this.overlayEl.addEventListener("click",()=>{this.options.closeOnOverlayClick&&this.close()}),this.container.insertBefore(this.overlayEl,this.wrapperEl)),E(this.wrapperEl,this.overlayEl,{ariaLabel:this.options.ariaLabel,ariaLabelledBy:this.options.ariaLabelledBy,modal:this.options.modal}),this.applyLayout()}bindGestures(){this.options.dragMode!=="none"&&(this.cleanupGestures=H(this.wrapperEl,this.handleEl,this.options.dragMode==="handle",{onStart:()=>{this.cancelCurrentAnimation(),this.state="dragging",this.dragStartH=this.currentH,this.wrapperEl.setAttribute("data-dragging","true")},onMove:(e,t)=>{let s=this.dragStartH-t,r=this.snapPixels(),n=r[r.length-1],a=r[0];if(s>n)s=n;else if(s<a){let d=a-s;s=a-Math.max(0,X(d))}this.currentH=s,this.render(s)},onEnd:e=>{this.wrapperEl.setAttribute("data-dragging","false");let t=-e;this.state="animating";let s=this.snapPixels(),r=P(this.currentH,t,s,400),n=s.filter(a=>a>0);if(n.length>0){let a=n[0];this.currentH<a*this.options.closeThreshold&&(r=0)}this.animateToH(r,t)}}))}cancelCurrentAnimation(){this.cancelAnimation&&(this.cancelAnimation(),this.cancelAnimation=null)}animateToH(e,t=0){let s=this.state==="open"||this.state==="dragging";this.cancelCurrentAnimation(),this.state="animating";let r=()=>{this.currentH=e,this.render(e),this.cancelAnimation=null;let a=this.pixelsToSnap(e);this.currentSnap=a,e<=.5?this.onClosed():(this.state="open",s||this.options.onOpen(),this.options.onSnap(a))};if(this.prefersReducedMotion()){r();return}let n={damping:this.options.damping,stiffness:this.options.stiffness,mass:this.options.mass};this.cancelAnimation=T(this.currentH,e,t,n,a=>{this.currentH=a,this.render(a)},r)}render(e){let t=window.innerHeight,s=Math.max(0,e),r=t-s;if(this.wrapperEl.style.visibility=s>.5?"visible":"hidden",this.wrapperEl.style.transform=`translate3d(0, ${r}px, 0)`,this.wrapperEl.style.height=s>.5?`${s}px`:"",this.overlayEl){let n=this.snapPixels(),a=n[n.length-1],d=Math.max(0,Math.min(1,s/(a*.3)));this.overlayEl.style.opacity=String(d),this.overlayEl.setAttribute("data-visible",s>1?"true":"false")}}resetToClosedState(){this.cancelCurrentAnimation(),this.currentH=0,this.currentSnap=0,this.render(0),this.state="closed",this.overlayEl&&(this.overlayEl.style.opacity="0",this.overlayEl.setAttribute("data-visible","false")),this.unlockScroll&&(this.unlockScroll(),this.unlockScroll=null),this.cleanupFocusTrap&&(this.cleanupFocusTrap(),this.cleanupFocusTrap=null),document.removeEventListener("keydown",this.onKeyDown)}onClosed(){this.resetToClosedState(),this.options.onClose()}open(e){if(this.destroyed||this.state==="open")return;let t=[...this.options.snapPoints].sort((r,n)=>r-n),s;if(e!==void 0){if(!t.includes(e)||e===0){console.warn(`[drwr] snap point ${e} not valid for open()`);return}s=e}else s=t[t.length-1];document.addEventListener("keydown",this.onKeyDown),this.animateToH(this.snapToPixels(s)),this.unlockScroll||(this.unlockScroll=b()),this.options.trapFocus&&!this.cleanupFocusTrap&&(this.cleanupFocusTrap=v(this.wrapperEl))}close(){this.destroyed||this.state==="closed"||(document.removeEventListener("keydown",this.onKeyDown),this.animateToH(0))}snapTo(e){if(!this.destroyed){if(!this.options.snapPoints.includes(e)){console.warn(`[drwr] snap point ${e} not in snapPoints`);return}e===0?this.close():(this.state==="closed"&&(document.addEventListener("keydown",this.onKeyDown),this.unlockScroll||(this.unlockScroll=b()),this.options.trapFocus&&!this.cleanupFocusTrap&&(this.cleanupFocusTrap=v(this.wrapperEl))),this.animateToH(this.snapToPixels(e)))}}destroy(){if(!this.destroyed){for(this.destroyed=!0,this.cancelCurrentAnimation(),document.removeEventListener("keydown",this.onKeyDown),this.cleanupGestures&&(this.cleanupGestures(),this.cleanupGestures=null),this.cleanupFocusTrap&&(this.cleanupFocusTrap(),this.cleanupFocusTrap=null),this.unlockScroll&&(this.unlockScroll(),this.unlockScroll=null),this.onPageShow&&(window.removeEventListener("pageshow",this.onPageShow),this.onPageShow=null),this.onResize&&(window.removeEventListener("resize",this.onResize),this.onResize=null),M(this.wrapperEl,this.overlayEl);this.contentEl.firstChild;)this.container.appendChild(this.contentEl.firstChild);this.wrapperEl.remove(),this.overlayEl?.remove()}}setSnapPoints(e){if(!this.destroyed&&(this.options.snapPoints=i.sanitizeSnapPoints(e),this.state!=="closed"&&this.currentSnap>0&&!this.options.snapPoints.includes(this.currentSnap))){let t=this.pixelsToSnap(this.currentH);this.animateToH(this.snapToPixels(t))}}refresh(){this.destroyed||(this.naturalContentH=this.getContentHeight(),this.state!=="closed"&&this.currentSnap>0&&(this.currentH=this.snapToPixels(this.currentSnap),this.render(this.currentH)))}setOptions(e){if(this.destroyed)return;if(e.snapPoints!==void 0&&(this.options.snapPoints=i.sanitizeSnapPoints(e.snapPoints)),e.damping!==void 0&&(this.options.damping=e.damping),e.stiffness!==void 0&&(this.options.stiffness=e.stiffness),e.mass!==void 0&&(this.options.mass=e.mass),e.closeThreshold!==void 0&&(this.options.closeThreshold=e.closeThreshold),e.topPadding!==void 0&&(this.options.topPadding=e.topPadding),e.closeOnEscape!==void 0&&(this.options.closeOnEscape=e.closeOnEscape),e.closeOnOverlayClick!==void 0&&(this.options.closeOnOverlayClick=e.closeOnOverlayClick),e.ariaLabel!==void 0&&(this.options.ariaLabel=e.ariaLabel),e.ariaLabelledBy!==void 0&&(this.options.ariaLabelledBy=e.ariaLabelledBy),e.modal!==void 0&&(this.options.modal=e.modal),e.maxWidth!==void 0&&(this.options.maxWidth=e.maxWidth),e.reducedMotion!==void 0&&(this.options.reducedMotion=e.reducedMotion),e.onOpen!==void 0&&(this.options.onOpen=e.onOpen),e.onClose!==void 0&&(this.options.onClose=e.onClose),e.onSnap!==void 0&&(this.options.onSnap=e.onSnap),(e.ariaLabel!==void 0||e.ariaLabelledBy!==void 0||e.modal!==void 0)&&E(this.wrapperEl,this.overlayEl,{ariaLabel:this.options.ariaLabel,ariaLabelledBy:this.options.ariaLabelledBy,modal:this.options.modal}),(e.width!==void 0||e.align!==void 0||e.sidePadding!==void 0||e.maxWidth!==void 0)&&(e.width!==void 0&&(this.options.width=e.width),e.align!==void 0&&(this.options.align=e.align),e.sidePadding!==void 0&&(this.options.sidePadding=e.sidePadding),this.applyLayout()),(e.topPadding!==void 0||e.snapPoints!==void 0)&&this.state!=="closed"&&this.currentSnap>0)if(e.snapPoints!==void 0&&!this.options.snapPoints.includes(this.currentSnap)){let s=this.pixelsToSnap(this.currentH);this.animateToH(this.snapToPixels(s))}else this.currentH=this.snapToPixels(this.currentSnap),this.render(this.currentH)}setLayout(e){this.destroyed||(e.width!==void 0&&(this.options.width=e.width),e.align!==void 0&&(this.options.align=e.align),e.sidePadding!==void 0&&(this.options.sidePadding=e.sidePadding),this.applyLayout())}get isOpen(){return this.state!=="closed"}get currentSnapPoint(){return this.currentSnap}};0&&(module.exports={Sheet});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/physics.ts","../src/gestures.ts","../src/accessibility.ts","../src/sheet.ts"],"sourcesContent":["export { Sheet } from \"./sheet.js\";\nexport type { SheetOptions, MutableSheetOptions, SheetAlign, DragMode } from \"./sheet.js\";\nexport type { SpringConfig } from \"./physics.js\";\n","export interface SpringConfig {\n damping: number;\n stiffness: number;\n mass: number;\n restThreshold: number;\n}\n\nexport const DEFAULT_SPRING: SpringConfig = {\n damping: 30,\n stiffness: 200,\n mass: 1,\n restThreshold: 0.5,\n};\n\nexport interface SpringState {\n position: number;\n velocity: number;\n}\n\n/**\n * Solve one step of a damped spring using semi-implicit Euler integration.\n * Returns the new position and velocity after `dt` seconds.\n */\nexport function springStep(\n current: number,\n target: number,\n velocity: number,\n dt: number,\n config: SpringConfig,\n): SpringState {\n const { stiffness, damping, mass } = config;\n const displacement = current - target;\n const springForce = -stiffness * displacement;\n const dampingForce = -damping * velocity;\n const acceleration = (springForce + dampingForce) / mass;\n\n const newVelocity = velocity + acceleration * dt;\n const newPosition = current + newVelocity * dt;\n\n return { position: newPosition, velocity: newVelocity };\n}\n\n/**\n * Returns true when the spring is close enough to rest.\n */\nexport function isAtRest(\n current: number,\n target: number,\n velocity: number,\n threshold: number = DEFAULT_SPRING.restThreshold,\n): boolean {\n return (\n Math.abs(current - target) < threshold && Math.abs(velocity) < threshold\n );\n}\n\n/**\n * Runs a spring animation via requestAnimationFrame. Returns a cancel function.\n */\nexport function animateSpring(\n from: number,\n to: number,\n initialVelocity: number,\n config: Partial<SpringConfig>,\n onUpdate: (value: number) => void,\n onComplete: () => void,\n): () => void {\n const cfg = { ...DEFAULT_SPRING, ...config };\n let position = from;\n let velocity = initialVelocity;\n let lastTime = 0;\n let frameId = 0;\n\n function tick(time: number) {\n if (lastTime === 0) {\n lastTime = time;\n frameId = requestAnimationFrame(tick);\n return;\n }\n\n // Cap dt to avoid spiral of death on tab-switch resume\n const dt = Math.min((time - lastTime) / 1000, 0.064);\n lastTime = time;\n\n const state = springStep(position, to, velocity, dt, cfg);\n position = state.position;\n velocity = state.velocity;\n\n onUpdate(position);\n\n if (isAtRest(position, to, velocity, cfg.restThreshold)) {\n onUpdate(to);\n onComplete();\n return;\n }\n\n frameId = requestAnimationFrame(tick);\n }\n\n frameId = requestAnimationFrame(tick);\n\n return () => cancelAnimationFrame(frameId);\n}\n\n/**\n * Tracks pointer velocity over the last N samples for flick detection.\n */\nexport class VelocityTracker {\n private samples: { time: number; value: number }[] = [];\n private maxSamples: number;\n\n constructor(maxSamples = 8) {\n this.maxSamples = maxSamples;\n }\n\n add(value: number) {\n this.samples.push({ time: performance.now(), value });\n if (this.samples.length > this.maxSamples) {\n this.samples.shift();\n }\n }\n\n /** Returns velocity in units per second. */\n getVelocity(): number {\n if (this.samples.length < 2) return 0;\n\n const recent = this.samples[this.samples.length - 1];\n // Use sample from ~50ms ago for stability\n let oldest = this.samples[0];\n for (let i = this.samples.length - 2; i >= 0; i--) {\n if (recent.time - this.samples[i].time >= 30) {\n oldest = this.samples[i];\n break;\n }\n }\n\n const dt = (recent.time - oldest.time) / 1000;\n if (dt === 0) return 0;\n\n return (recent.value - oldest.value) / dt;\n }\n\n reset() {\n this.samples = [];\n }\n}\n\n/**\n * Given a set of snap points and a release position + velocity,\n * determine which snap point to target.\n */\nexport function resolveSnapPoint(\n position: number,\n velocity: number,\n snapPoints: number[],\n flickThreshold = 400,\n hardFlickThreshold = 1200,\n): number {\n if (snapPoints.length === 0) return position;\n if (snapPoints.length === 1) return snapPoints[0];\n\n const sorted = [...snapPoints].sort((a, b) => a - b);\n\n // Hard flick: skip all intermediate snaps, go to the extreme\n if (Math.abs(velocity) > hardFlickThreshold) {\n return velocity > 0 ? sorted[sorted.length - 1] : sorted[0];\n }\n\n // Normal flick: go to next snap in the flick direction\n if (Math.abs(velocity) > flickThreshold) {\n if (velocity > 0) {\n for (const sp of sorted) {\n if (sp > position + 2) return sp;\n }\n return sorted[sorted.length - 1];\n } else {\n for (let i = sorted.length - 1; i >= 0; i--) {\n if (sorted[i] < position - 2) return sorted[i];\n }\n return sorted[0];\n }\n }\n\n // No flick: snap to nearest\n let closest = sorted[0];\n let closestDist = Math.abs(position - sorted[0]);\n for (let i = 1; i < sorted.length; i++) {\n const dist = Math.abs(position - sorted[i]);\n if (dist < closestDist) {\n closest = sorted[i];\n closestDist = dist;\n }\n }\n return closest;\n}\n","import { VelocityTracker } from \"./physics.js\";\n\nexport interface GestureCallbacks {\n onStart: (y: number) => void;\n onMove: (y: number, deltaY: number) => void;\n onEnd: (velocity: number) => void;\n}\n\ninterface GestureState {\n active: boolean;\n dragging: boolean;\n startY: number;\n currentY: number;\n directionLocked: boolean;\n lockedAxis: \"x\" | \"y\" | null;\n startX: number;\n currentX: number;\n scrolling: boolean;\n scrollTarget: HTMLElement | null;\n}\n\nconst DIRECTION_LOCK_THRESHOLD = 6;\nconst SCROLL_LOCK_THRESHOLD = 3;\n\nfunction findScrollableAncestor(\n target: EventTarget | null,\n sheetEl: HTMLElement,\n): HTMLElement | null {\n let el = target as HTMLElement | null;\n while (el && el !== sheetEl) {\n if (el.scrollHeight > el.clientHeight) {\n const style = getComputedStyle(el);\n const overflowY = style.overflowY;\n if (overflowY === \"auto\" || overflowY === \"scroll\") {\n return el;\n }\n }\n el = el.parentElement;\n }\n return null;\n}\n\nexport function attachGestures(\n sheetEl: HTMLElement,\n handleEl: HTMLElement | null,\n handleOnly: boolean,\n callbacks: GestureCallbacks,\n): () => void {\n const tracker = new VelocityTracker();\n const state: GestureState = {\n active: false,\n dragging: false,\n startY: 0,\n currentY: 0,\n directionLocked: false,\n lockedAxis: null,\n startX: 0,\n currentX: 0,\n scrolling: false,\n scrollTarget: null,\n };\n\n function onPointerDown(e: PointerEvent) {\n const isHandle = handleEl?.contains(e.target as Node);\n if (handleOnly && !isHandle) return;\n const scrollable = isHandle\n ? null\n : findScrollableAncestor(e.target, sheetEl);\n\n state.active = true;\n state.dragging = false;\n state.startY = e.clientY;\n state.currentY = e.clientY;\n state.startX = e.clientX;\n state.currentX = e.clientX;\n state.directionLocked = false;\n state.lockedAxis = null;\n state.scrolling = false;\n state.scrollTarget = scrollable;\n\n tracker.reset();\n tracker.add(e.clientY);\n\n sheetEl.setPointerCapture(e.pointerId);\n }\n\n function onPointerMove(e: PointerEvent) {\n if (!state.active || state.scrolling) return;\n\n state.currentY = e.clientY;\n state.currentX = e.clientX;\n\n const dx = Math.abs(state.currentX - state.startX);\n const dy = Math.abs(state.currentY - state.startY);\n const deltaY = state.currentY - state.startY;\n\n // Direction locking\n if (!state.directionLocked) {\n if (dx > DIRECTION_LOCK_THRESHOLD || dy > DIRECTION_LOCK_THRESHOLD) {\n state.directionLocked = true;\n state.lockedAxis = dy >= dx ? \"y\" : \"x\";\n }\n if (dy < SCROLL_LOCK_THRESHOLD) return;\n }\n\n if (state.lockedAxis === \"x\") return;\n\n // Scroll vs drag disambiguation (decided once)\n if (state.scrollTarget && !state.dragging) {\n const st = state.scrollTarget;\n const atTop = st.scrollTop <= 0;\n const atBottom = st.scrollTop + st.clientHeight >= st.scrollHeight - 1;\n const draggingDown = deltaY > 0;\n\n if (draggingDown && atTop) {\n // At top, pulling down → drag sheet\n } else if (!draggingDown && atBottom) {\n // At bottom, pulling up → drag sheet\n } else {\n // Let the content scroll\n state.scrolling = true;\n return;\n }\n }\n\n // Begin drag on first qualifying move\n if (!state.dragging) {\n state.dragging = true;\n callbacks.onStart(state.startY);\n }\n\n e.preventDefault();\n tracker.add(e.clientY);\n callbacks.onMove(state.currentY, deltaY);\n }\n\n function onPointerUp(e: PointerEvent) {\n if (!state.active) return;\n state.active = false;\n sheetEl.releasePointerCapture(e.pointerId);\n\n if (!state.dragging) return;\n state.dragging = false;\n\n const velocity = tracker.getVelocity();\n callbacks.onEnd(velocity);\n }\n\n function onPointerCancel(e: PointerEvent) {\n if (!state.active) return;\n state.active = false;\n sheetEl.releasePointerCapture(e.pointerId);\n\n if (!state.dragging) return;\n state.dragging = false;\n callbacks.onEnd(0);\n }\n\n sheetEl.addEventListener(\"pointerdown\", onPointerDown);\n sheetEl.addEventListener(\"pointermove\", onPointerMove);\n sheetEl.addEventListener(\"pointerup\", onPointerUp);\n sheetEl.addEventListener(\"pointercancel\", onPointerCancel);\n\n const onContextMenu = (e: Event) => {\n if (state.active) e.preventDefault();\n };\n sheetEl.addEventListener(\"contextmenu\", onContextMenu);\n\n return () => {\n sheetEl.removeEventListener(\"pointerdown\", onPointerDown);\n sheetEl.removeEventListener(\"pointermove\", onPointerMove);\n sheetEl.removeEventListener(\"pointerup\", onPointerUp);\n sheetEl.removeEventListener(\"pointercancel\", onPointerCancel);\n sheetEl.removeEventListener(\"contextmenu\", onContextMenu);\n };\n}\n","const FOCUSABLE_SELECTORS = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(\", \");\n\n/**\n * Creates a focus trap within the given element.\n * Returns a cleanup function.\n */\nexport function createFocusTrap(container: HTMLElement): () => void {\n let previousFocus: HTMLElement | null = null;\n\n function getFocusable(): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS));\n }\n\n function onKeyDown(e: KeyboardEvent) {\n if (e.key !== \"Tab\") return;\n\n const focusable = getFocusable();\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first || !container.contains(document.activeElement)) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last || !container.contains(document.activeElement)) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n\n function activate() {\n previousFocus = document.activeElement as HTMLElement | null;\n container.addEventListener(\"keydown\", onKeyDown);\n\n // Focus first focusable element or the container itself\n requestAnimationFrame(() => {\n const focusable = getFocusable();\n if (focusable.length > 0) {\n focusable[0].focus();\n } else {\n container.focus();\n }\n });\n }\n\n function deactivate() {\n container.removeEventListener(\"keydown\", onKeyDown);\n previousFocus?.focus();\n previousFocus = null;\n }\n\n activate();\n\n return deactivate;\n}\n\nexport interface AriaOptions {\n ariaLabel?: string;\n ariaLabelledBy?: string;\n modal?: boolean;\n}\n\n/**\n * Applies ARIA attributes for a modal dialog.\n */\nexport function applyAriaAttributes(\n sheetEl: HTMLElement,\n overlayEl: HTMLElement | null,\n opts: AriaOptions = {},\n) {\n sheetEl.setAttribute(\"role\", \"dialog\");\n sheetEl.setAttribute(\"aria-modal\", opts.modal !== false ? \"true\" : \"false\");\n\n if (opts.ariaLabelledBy) {\n sheetEl.setAttribute(\"aria-labelledby\", opts.ariaLabelledBy);\n sheetEl.removeAttribute(\"aria-label\");\n } else {\n sheetEl.setAttribute(\"aria-label\", opts.ariaLabel || \"Sheet\");\n sheetEl.removeAttribute(\"aria-labelledby\");\n }\n\n if (overlayEl) {\n overlayEl.setAttribute(\"aria-hidden\", \"true\");\n }\n}\n\n/**\n * Removes ARIA attributes when the sheet is destroyed.\n */\nexport function removeAriaAttributes(\n sheetEl: HTMLElement,\n overlayEl: HTMLElement | null,\n) {\n sheetEl.removeAttribute(\"role\");\n sheetEl.removeAttribute(\"aria-modal\");\n if (overlayEl) {\n overlayEl.removeAttribute(\"aria-hidden\");\n }\n}\n\n/**\n * iOS-safe scroll lock. Prevents background scrolling without position:fixed\n * to avoid the iOS Safari viewport jump when the address bar is visible.\n */\nexport function lockScroll(): () => void {\n const body = document.body;\n const html = document.documentElement;\n\n const originalBodyOverflow = body.style.overflow;\n const originalHtmlOverflow = html.style.overflow;\n\n html.style.overflow = \"hidden\";\n body.style.overflow = \"hidden\";\n\n // On iOS Safari, overflow:hidden on body/html doesn't fully prevent\n // scroll on its own. We block touchmove on the document as a fallback.\n const preventTouch = (e: TouchEvent) => {\n // Allow scrolling inside the sheet content\n let el = e.target as HTMLElement | null;\n while (el) {\n if (el.classList?.contains(\"drwr-content\")) return;\n el = el.parentElement;\n }\n e.preventDefault();\n };\n\n document.addEventListener(\"touchmove\", preventTouch, { passive: false });\n\n return () => {\n body.style.overflow = originalBodyOverflow;\n html.style.overflow = originalHtmlOverflow;\n document.removeEventListener(\"touchmove\", preventTouch);\n };\n}\n","import {\n animateSpring,\n resolveSnapPoint,\n type SpringConfig,\n} from \"./physics.js\";\nimport { attachGestures } from \"./gestures.js\";\nimport {\n applyAriaAttributes,\n removeAriaAttributes,\n createFocusTrap,\n lockScroll,\n} from \"./accessibility.js\";\n\nexport type SheetAlign = \"center\" | \"left\" | \"right\";\nexport type DragMode = \"handle\" | \"sheet\" | \"none\";\n\nexport interface SheetOptions {\n snapPoints?: number[];\n defaultSnap?: number;\n overlay?: boolean;\n dragHandle?: boolean;\n damping?: number;\n stiffness?: number;\n mass?: number;\n closeThreshold?: number;\n /** Top padding in px for the maximum open height. Default: 40. */\n topPadding?: number;\n /** Width as a percentage of the viewport (0–100). Default: 60 on desktop, 100 on mobile. */\n width?: number;\n /** Horizontal alignment. Default: \"center\". */\n align?: SheetAlign;\n /** Minimum side padding in px. Default: 0. */\n sidePadding?: number;\n /** Max width in px. 0 = no max. Default: 0. */\n maxWidth?: number;\n /** Which part of the sheet initiates drag. Default: \"sheet\". */\n dragMode?: DragMode;\n /** Accessible label for the sheet dialog. Default: \"Sheet\". */\n ariaLabel?: string;\n /** ID of the element labelling the sheet. Overrides ariaLabel when set. */\n ariaLabelledBy?: string;\n /** Close on Escape key. Default: true. */\n closeOnEscape?: boolean;\n /** Close on overlay click. Default: true. */\n closeOnOverlayClick?: boolean;\n /** Trap focus inside the sheet. Default: true. */\n trapFocus?: boolean;\n /** Set aria-modal. Default: true. */\n modal?: boolean;\n /** Honour prefers-reduced-motion by skipping spring animation. Default: true. */\n reducedMotion?: boolean;\n /** Called when open animation completes. */\n onOpen?: () => void;\n /** Called when close animation completes. */\n onClose?: () => void;\n onSnap?: (point: number) => void;\n}\n\n/** Subset of SheetOptions that can be changed at runtime via setOptions(). */\nexport type MutableSheetOptions = Pick<\n SheetOptions,\n | \"snapPoints\"\n | \"damping\"\n | \"stiffness\"\n | \"mass\"\n | \"closeThreshold\"\n | \"topPadding\"\n | \"width\"\n | \"align\"\n | \"sidePadding\"\n | \"maxWidth\"\n | \"ariaLabel\"\n | \"ariaLabelledBy\"\n | \"closeOnEscape\"\n | \"closeOnOverlayClick\"\n | \"modal\"\n | \"reducedMotion\"\n | \"onOpen\"\n | \"onClose\"\n | \"onSnap\"\n>;\n\ntype SheetState = \"closed\" | \"open\" | \"dragging\" | \"animating\";\n\n/** Logarithmic resistance like vaul: soft, organic feel past bounds. */\nfunction dampen(v: number): number {\n return 8 * (Math.log(v + 1) - 2);\n}\n\nexport class Sheet {\n private container: HTMLElement;\n private options: Required<SheetOptions>;\n\n private wrapperEl!: HTMLElement;\n private overlayEl: HTMLElement | null = null;\n private handleEl: HTMLElement | null = null;\n private contentEl!: HTMLElement;\n\n private state: SheetState = \"closed\";\n private currentSnap = 0;\n // Current height of the sheet in pixels (0 = closed, positive = visible)\n private currentH = 0;\n private dragStartH = 0;\n\n private cancelAnimation: (() => void) | null = null;\n private cleanupGestures: (() => void) | null = null;\n private cleanupFocusTrap: (() => void) | null = null;\n private unlockScroll: (() => void) | null = null;\n private onPageShow: ((e: PageTransitionEvent) => void) | null = null;\n private onResize: (() => void) | null = null;\n private naturalContentH = 0;\n private destroyed = false;\n\n private static sanitizeSnapPoints(raw: number[]): number[] {\n const valid = raw.filter((n) => Number.isFinite(n) && n >= 0 && n <= 1);\n const deduped = [...new Set(valid)].sort((a, b) => a - b);\n if (deduped.length === 0) return [0, 1];\n if (!deduped.includes(0)) deduped.unshift(0);\n return deduped;\n }\n\n private prefersReducedMotion(): boolean {\n return (\n this.options.reducedMotion &&\n typeof window !== \"undefined\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n );\n }\n\n constructor(container: HTMLElement, options: SheetOptions = {}) {\n this.container = container;\n const snaps = Sheet.sanitizeSnapPoints(options.snapPoints ?? [0, 1]);\n this.options = {\n snapPoints: snaps,\n defaultSnap: options.defaultSnap ?? 0,\n overlay: options.overlay ?? true,\n dragHandle: options.dragHandle ?? true,\n damping: options.damping ?? 30,\n stiffness: options.stiffness ?? 200,\n mass: options.mass ?? 1,\n closeThreshold: options.closeThreshold ?? 0.2,\n topPadding: options.topPadding ?? 40,\n width: options.width ?? (window.innerWidth >= 768 ? 60 : 100),\n align: options.align ?? \"center\",\n sidePadding: options.sidePadding ?? 0,\n maxWidth: options.maxWidth ?? 0,\n dragMode: options.dragMode ?? \"sheet\",\n ariaLabel: options.ariaLabel ?? \"Sheet\",\n ariaLabelledBy: options.ariaLabelledBy ?? \"\",\n closeOnEscape: options.closeOnEscape ?? true,\n closeOnOverlayClick: options.closeOnOverlayClick ?? true,\n trapFocus: options.trapFocus ?? true,\n modal: options.modal ?? true,\n reducedMotion: options.reducedMotion ?? true,\n onOpen: options.onOpen ?? (() => {}),\n onClose: options.onClose ?? (() => {}),\n onSnap: options.onSnap ?? (() => {}),\n };\n\n this.buildDOM();\n this.container.removeAttribute(\"hidden\");\n this.naturalContentH = this.getContentHeight();\n this.bindGestures();\n\n const defaultSnap = this.options.defaultSnap;\n if (defaultSnap > 0 && this.options.snapPoints.includes(defaultSnap)) {\n this.currentH = this.snapToPixels(defaultSnap);\n this.render(this.currentH);\n this.state = \"open\";\n this.currentSnap = defaultSnap;\n document.addEventListener(\"keydown\", this.onKeyDown);\n if (!this.unlockScroll) this.unlockScroll = lockScroll();\n if (this.options.trapFocus && !this.cleanupFocusTrap) {\n this.cleanupFocusTrap = createFocusTrap(this.wrapperEl);\n }\n queueMicrotask(() => {\n if (this.destroyed) return;\n this.options.onOpen();\n });\n } else {\n this.currentH = 0;\n this.render(0);\n }\n this.onPageShow = (e: PageTransitionEvent) => {\n if (e.persisted && this.state !== \"closed\") {\n this.resetToClosedState();\n }\n };\n window.addEventListener(\"pageshow\", this.onPageShow);\n\n this.onResize = () => {\n this.applyLayout();\n this.naturalContentH = this.getContentHeight();\n if (this.state !== \"closed\" && this.currentSnap > 0) {\n this.currentH = this.snapToPixels(this.currentSnap);\n this.render(this.currentH);\n }\n };\n window.addEventListener(\"resize\", this.onResize);\n }\n\n // --- Snap point conversion ---\n\n private getContentHeight(): number {\n return this.contentEl.scrollHeight + (this.handleEl?.offsetHeight ?? 0);\n }\n\n private maxSheetH(): number {\n return window.innerHeight - this.options.topPadding;\n }\n\n /** Convert a normalized snap point (0–1) to available viewport height. */\n private snapToPixels(snap: number): number {\n if (snap <= 0) return 0;\n const maxH = this.maxSheetH();\n return Math.min(snap, 1) * maxH;\n }\n\n /** Get all snap points as pixel heights, sorted ascending. */\n private snapPixels(): number[] {\n return this.options.snapPoints\n .map((s) => this.snapToPixels(s))\n .sort((a, b) => a - b);\n }\n\n /** Find the normalized snap point closest to a pixel height. */\n private pixelsToSnap(px: number): number {\n const snaps = this.options.snapPoints;\n let closest = snaps[0];\n let closestDist = Math.abs(px - this.snapToPixels(snaps[0]));\n for (let i = 1; i < snaps.length; i++) {\n const dist = Math.abs(px - this.snapToPixels(snaps[i]));\n if (dist < closestDist) {\n closest = snaps[i];\n closestDist = dist;\n }\n }\n return closest;\n }\n\n // --- DOM ---\n\n private applyLayout() {\n const { width, align, sidePadding, maxWidth } = this.options;\n const el = this.wrapperEl;\n\n const vw = window.innerWidth;\n const clampedWidth = Math.max(10, Math.min(100, width));\n const pad = Math.max(0, Math.min(sidePadding, vw / 2 - 10));\n\n let sheetW = Math.max(20, Math.min(vw * (clampedWidth / 100), vw - pad * 2));\n if (maxWidth > 0) sheetW = Math.min(sheetW, maxWidth);\n\n let left: number;\n if (align === \"left\") {\n left = pad;\n } else if (align === \"right\") {\n left = vw - sheetW - pad;\n } else {\n left = (vw - sheetW) / 2;\n }\n\n el.style.left = `${left}px`;\n el.style.right = \"auto\";\n el.style.width = `${sheetW}px`;\n el.style.margin = \"0\";\n }\n\n private buildDOM() {\n this.wrapperEl = document.createElement(\"div\");\n this.wrapperEl.className = \"drwr-sheet\";\n this.wrapperEl.setAttribute(\"tabindex\", \"-1\");\n\n if (this.options.dragHandle) {\n this.handleEl = document.createElement(\"div\");\n this.handleEl.className = \"drwr-handle\";\n this.wrapperEl.appendChild(this.handleEl);\n }\n\n this.contentEl = document.createElement(\"div\");\n this.contentEl.className = \"drwr-content\";\n\n while (this.container.firstChild) {\n this.contentEl.appendChild(this.container.firstChild);\n }\n this.wrapperEl.appendChild(this.contentEl);\n this.container.appendChild(this.wrapperEl);\n\n if (this.options.overlay) {\n this.overlayEl = document.createElement(\"div\");\n this.overlayEl.className = \"drwr-overlay\";\n this.overlayEl.addEventListener(\"click\", () => {\n if (this.options.closeOnOverlayClick) this.close();\n });\n this.container.insertBefore(this.overlayEl, this.wrapperEl);\n }\n\n applyAriaAttributes(this.wrapperEl, this.overlayEl, {\n ariaLabel: this.options.ariaLabel,\n ariaLabelledBy: this.options.ariaLabelledBy,\n modal: this.options.modal,\n });\n this.applyLayout();\n }\n\n // --- Gestures ---\n\n private bindGestures() {\n if (this.options.dragMode === \"none\") return;\n this.cleanupGestures = attachGestures(\n this.wrapperEl,\n this.handleEl,\n this.options.dragMode === \"handle\",\n {\n onStart: () => {\n this.cancelCurrentAnimation();\n this.state = \"dragging\";\n this.dragStartH = this.currentH;\n this.wrapperEl.setAttribute(\"data-dragging\", \"true\");\n },\n onMove: (_y, deltaY) => {\n // deltaY positive = finger moved down = sheet should shrink\n let newH = this.dragStartH - deltaY;\n\n const snaps = this.snapPixels();\n const maxH = snaps[snaps.length - 1];\n const minH = snaps[0];\n\n if (newH > maxH) {\n newH = maxH;\n } else if (newH < minH) {\n const overflow = minH - newH;\n newH = minH - Math.max(0, dampen(overflow));\n }\n\n this.currentH = newH;\n this.render(newH);\n },\n onEnd: (velocityPx) => {\n this.wrapperEl.setAttribute(\"data-dragging\", \"false\");\n const velocityH = -velocityPx;\n this.state = \"animating\";\n\n const snaps = this.snapPixels();\n let targetH = resolveSnapPoint(this.currentH, velocityH, snaps, 400);\n\n // closeThreshold: if the sheet is dragged below (lowestNonZeroSnap * threshold), close it\n const nonZeroSnaps = snaps.filter((s) => s > 0);\n if (nonZeroSnaps.length > 0) {\n const lowestNonZero = nonZeroSnaps[0];\n if (this.currentH < lowestNonZero * this.options.closeThreshold) {\n targetH = 0;\n }\n }\n\n this.animateToH(targetH, velocityH);\n },\n },\n );\n }\n\n // --- Animation ---\n\n private cancelCurrentAnimation() {\n if (this.cancelAnimation) {\n this.cancelAnimation();\n this.cancelAnimation = null;\n }\n }\n\n private animateToH(targetH: number, initialVelocity = 0) {\n const wasOpen = this.state === \"open\" || this.state === \"dragging\";\n this.cancelCurrentAnimation();\n this.state = \"animating\";\n const onDone = () => {\n this.currentH = targetH;\n this.render(targetH);\n this.cancelAnimation = null;\n\n const snap = this.pixelsToSnap(targetH);\n this.currentSnap = snap;\n\n if (targetH <= 0.5) {\n this.onClosed();\n } else {\n this.state = \"open\";\n if (!wasOpen) this.options.onOpen();\n this.options.onSnap(snap);\n }\n };\n\n if (this.prefersReducedMotion()) {\n onDone();\n return;\n }\n\n const springConfig: Partial<SpringConfig> = {\n damping: this.options.damping,\n stiffness: this.options.stiffness,\n mass: this.options.mass,\n };\n\n this.cancelAnimation = animateSpring(\n this.currentH,\n targetH,\n initialVelocity,\n springConfig,\n (value) => {\n this.currentH = value;\n this.render(value);\n },\n onDone,\n );\n }\n\n // --- Rendering ---\n\n private render(h: number) {\n const viewportH = window.innerHeight;\n const visibleH = Math.max(0, h);\n const translateY = viewportH - visibleH;\n\n this.wrapperEl.style.visibility = visibleH > 0.5 ? \"visible\" : \"hidden\";\n\n this.wrapperEl.style.transform = `translate3d(0, ${translateY}px, 0)`;\n this.wrapperEl.style.height = visibleH > 0.5 ? `${visibleH}px` : \"\";\n\n if (this.overlayEl) {\n const snaps = this.snapPixels();\n const maxH = snaps[snaps.length - 1];\n const opacity = Math.max(0, Math.min(1, visibleH / (maxH * 0.3)));\n this.overlayEl.style.opacity = String(opacity);\n this.overlayEl.setAttribute(\"data-visible\", visibleH > 1 ? \"true\" : \"false\");\n }\n }\n\n // --- State changes ---\n\n /** Shared cleanup: visually close and tear down locks/traps. Does NOT fire callbacks. */\n private resetToClosedState() {\n this.cancelCurrentAnimation();\n this.currentH = 0;\n this.currentSnap = 0;\n this.render(0);\n this.state = \"closed\";\n\n if (this.overlayEl) {\n this.overlayEl.style.opacity = \"0\";\n this.overlayEl.setAttribute(\"data-visible\", \"false\");\n }\n if (this.unlockScroll) {\n this.unlockScroll();\n this.unlockScroll = null;\n }\n if (this.cleanupFocusTrap) {\n this.cleanupFocusTrap();\n this.cleanupFocusTrap = null;\n }\n document.removeEventListener(\"keydown\", this.onKeyDown);\n }\n\n private onClosed() {\n this.resetToClosedState();\n this.options.onClose();\n }\n\n // --- Keyboard ---\n\n private onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && this.state !== \"closed\" && this.options.closeOnEscape) {\n this.close();\n }\n };\n\n // --- Public API ---\n\n open(point?: number) {\n if (this.destroyed || this.state === \"open\") return;\n\n const sorted = [...this.options.snapPoints].sort((a, b) => a - b);\n let target: number;\n if (point !== undefined) {\n if (!sorted.includes(point) || point === 0) {\n console.warn(`[drwr] snap point ${point} not valid for open()`);\n return;\n }\n target = point;\n } else {\n target = sorted[sorted.length - 1];\n }\n\n document.addEventListener(\"keydown\", this.onKeyDown);\n this.animateToH(this.snapToPixels(target));\n if (!this.unlockScroll) this.unlockScroll = lockScroll();\n if (this.options.trapFocus && !this.cleanupFocusTrap) {\n this.cleanupFocusTrap = createFocusTrap(this.wrapperEl);\n }\n }\n\n close() {\n if (this.destroyed || this.state === \"closed\") return;\n document.removeEventListener(\"keydown\", this.onKeyDown);\n this.animateToH(0);\n }\n\n snapTo(point: number) {\n if (this.destroyed) return;\n if (!this.options.snapPoints.includes(point)) {\n console.warn(`[drwr] snap point ${point} not in snapPoints`);\n return;\n }\n if (point === 0) {\n this.close();\n } else {\n if (this.state === \"closed\") {\n document.addEventListener(\"keydown\", this.onKeyDown);\n if (!this.unlockScroll) this.unlockScroll = lockScroll();\n if (this.options.trapFocus && !this.cleanupFocusTrap) {\n this.cleanupFocusTrap = createFocusTrap(this.wrapperEl);\n }\n }\n this.animateToH(this.snapToPixels(point));\n }\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n\n this.cancelCurrentAnimation();\n document.removeEventListener(\"keydown\", this.onKeyDown);\n\n if (this.cleanupGestures) {\n this.cleanupGestures();\n this.cleanupGestures = null;\n }\n if (this.cleanupFocusTrap) {\n this.cleanupFocusTrap();\n this.cleanupFocusTrap = null;\n }\n if (this.unlockScroll) {\n this.unlockScroll();\n this.unlockScroll = null;\n }\n if (this.onPageShow) {\n window.removeEventListener(\"pageshow\", this.onPageShow);\n this.onPageShow = null;\n }\n if (this.onResize) {\n window.removeEventListener(\"resize\", this.onResize);\n this.onResize = null;\n }\n\n removeAriaAttributes(this.wrapperEl, this.overlayEl);\n\n while (this.contentEl.firstChild) {\n this.container.appendChild(this.contentEl.firstChild);\n }\n\n this.wrapperEl.remove();\n this.overlayEl?.remove();\n\n }\n\n setSnapPoints(points: number[]) {\n if (this.destroyed) return;\n this.options.snapPoints = Sheet.sanitizeSnapPoints(points);\n\n if (this.state !== \"closed\" && this.currentSnap > 0) {\n if (!this.options.snapPoints.includes(this.currentSnap)) {\n const nearest = this.pixelsToSnap(this.currentH);\n this.animateToH(this.snapToPixels(nearest));\n }\n }\n }\n\n /** Re-measure content height and recompute current position. Call after dynamic content changes. */\n refresh() {\n if (this.destroyed) return;\n this.naturalContentH = this.getContentHeight();\n if (this.state !== \"closed\" && this.currentSnap > 0) {\n this.currentH = this.snapToPixels(this.currentSnap);\n this.render(this.currentH);\n }\n }\n\n setOptions(opts: MutableSheetOptions) {\n if (this.destroyed) return;\n if (opts.snapPoints !== undefined) this.options.snapPoints = Sheet.sanitizeSnapPoints(opts.snapPoints);\n if (opts.damping !== undefined) this.options.damping = opts.damping;\n if (opts.stiffness !== undefined) this.options.stiffness = opts.stiffness;\n if (opts.mass !== undefined) this.options.mass = opts.mass;\n if (opts.closeThreshold !== undefined) this.options.closeThreshold = opts.closeThreshold;\n if (opts.topPadding !== undefined) this.options.topPadding = opts.topPadding;\n if (opts.closeOnEscape !== undefined) this.options.closeOnEscape = opts.closeOnEscape;\n if (opts.closeOnOverlayClick !== undefined) this.options.closeOnOverlayClick = opts.closeOnOverlayClick;\n if (opts.ariaLabel !== undefined) this.options.ariaLabel = opts.ariaLabel;\n if (opts.ariaLabelledBy !== undefined) this.options.ariaLabelledBy = opts.ariaLabelledBy;\n if (opts.modal !== undefined) this.options.modal = opts.modal;\n if (opts.maxWidth !== undefined) this.options.maxWidth = opts.maxWidth;\n if (opts.reducedMotion !== undefined) this.options.reducedMotion = opts.reducedMotion;\n if (opts.onOpen !== undefined) this.options.onOpen = opts.onOpen;\n if (opts.onClose !== undefined) this.options.onClose = opts.onClose;\n if (opts.onSnap !== undefined) this.options.onSnap = opts.onSnap;\n\n if (opts.ariaLabel !== undefined || opts.ariaLabelledBy !== undefined || opts.modal !== undefined) {\n applyAriaAttributes(this.wrapperEl, this.overlayEl, {\n ariaLabel: this.options.ariaLabel,\n ariaLabelledBy: this.options.ariaLabelledBy,\n modal: this.options.modal,\n });\n }\n if (opts.width !== undefined || opts.align !== undefined || opts.sidePadding !== undefined || opts.maxWidth !== undefined) {\n if (opts.width !== undefined) this.options.width = opts.width;\n if (opts.align !== undefined) this.options.align = opts.align;\n if (opts.sidePadding !== undefined) this.options.sidePadding = opts.sidePadding;\n this.applyLayout();\n }\n\n // Recompute position when size-affecting options changed\n const sizeChanged = opts.topPadding !== undefined || opts.snapPoints !== undefined;\n if (sizeChanged && this.state !== \"closed\" && this.currentSnap > 0) {\n if (opts.snapPoints !== undefined && !this.options.snapPoints.includes(this.currentSnap)) {\n const nearest = this.pixelsToSnap(this.currentH);\n this.animateToH(this.snapToPixels(nearest));\n } else {\n this.currentH = this.snapToPixels(this.currentSnap);\n this.render(this.currentH);\n }\n }\n }\n\n setLayout(opts: { width?: number; align?: SheetAlign; sidePadding?: number }) {\n if (this.destroyed) return;\n if (opts.width !== undefined) this.options.width = opts.width;\n if (opts.align !== undefined) this.options.align = opts.align;\n if (opts.sidePadding !== undefined) this.options.sidePadding = opts.sidePadding;\n this.applyLayout();\n }\n\n get isOpen(): boolean {\n return this.state !== \"closed\";\n }\n\n get currentSnapPoint(): number {\n return this.currentSnap;\n }\n}\n"],"mappings":"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","names":["index_exports","__export","Sheet","__toCommonJS","DEFAULT_SPRING","springStep","current","target","velocity","dt","config","stiffness","damping","mass","displacement","springForce","dampingForce","acceleration","newVelocity","isAtRest","threshold","animateSpring","from","to","initialVelocity","onUpdate","onComplete","cfg","position","lastTime","frameId","tick","time","state","VelocityTracker","maxSamples","value","recent","oldest","i","resolveSnapPoint","snapPoints","flickThreshold","hardFlickThreshold","sorted","a","b","sp","closest","closestDist","dist","DIRECTION_LOCK_THRESHOLD","SCROLL_LOCK_THRESHOLD","findScrollableAncestor","target","sheetEl","el","overflowY","attachGestures","handleEl","handleOnly","callbacks","tracker","VelocityTracker","state","onPointerDown","e","isHandle","scrollable","onPointerMove","dx","dy","deltaY","st","atTop","atBottom","draggingDown","onPointerUp","velocity","onPointerCancel","onContextMenu","FOCUSABLE_SELECTORS","createFocusTrap","container","previousFocus","getFocusable","onKeyDown","e","focusable","first","last","activate","deactivate","applyAriaAttributes","sheetEl","overlayEl","opts","removeAriaAttributes","lockScroll","body","html","originalBodyOverflow","originalHtmlOverflow","preventTouch","el","dampen","v","Sheet","_Sheet","container","options","snaps","defaultSnap","lockScroll","createFocusTrap","e","raw","valid","n","deduped","a","b","snap","maxH","s","px","closest","closestDist","i","dist","width","align","sidePadding","maxWidth","el","vw","clampedWidth","pad","sheetW","left","applyAriaAttributes","attachGestures","_y","deltaY","newH","minH","overflow","velocityPx","velocityH","targetH","resolveSnapPoint","nonZeroSnaps","lowestNonZero","initialVelocity","wasOpen","onDone","springConfig","animateSpring","value","h","viewportH","visibleH","translateY","opacity","point","sorted","target","removeAriaAttributes","points","nearest","opts"]}
@@ -0,0 +1,110 @@
1
+ type SheetAlign = "center" | "left" | "right";
2
+ type DragMode = "handle" | "sheet" | "none";
3
+ interface SheetOptions {
4
+ snapPoints?: number[];
5
+ defaultSnap?: number;
6
+ overlay?: boolean;
7
+ dragHandle?: boolean;
8
+ damping?: number;
9
+ stiffness?: number;
10
+ mass?: number;
11
+ closeThreshold?: number;
12
+ /** Top padding in px for the maximum open height. Default: 40. */
13
+ topPadding?: number;
14
+ /** Width as a percentage of the viewport (0–100). Default: 60 on desktop, 100 on mobile. */
15
+ width?: number;
16
+ /** Horizontal alignment. Default: "center". */
17
+ align?: SheetAlign;
18
+ /** Minimum side padding in px. Default: 0. */
19
+ sidePadding?: number;
20
+ /** Max width in px. 0 = no max. Default: 0. */
21
+ maxWidth?: number;
22
+ /** Which part of the sheet initiates drag. Default: "sheet". */
23
+ dragMode?: DragMode;
24
+ /** Accessible label for the sheet dialog. Default: "Sheet". */
25
+ ariaLabel?: string;
26
+ /** ID of the element labelling the sheet. Overrides ariaLabel when set. */
27
+ ariaLabelledBy?: string;
28
+ /** Close on Escape key. Default: true. */
29
+ closeOnEscape?: boolean;
30
+ /** Close on overlay click. Default: true. */
31
+ closeOnOverlayClick?: boolean;
32
+ /** Trap focus inside the sheet. Default: true. */
33
+ trapFocus?: boolean;
34
+ /** Set aria-modal. Default: true. */
35
+ modal?: boolean;
36
+ /** Honour prefers-reduced-motion by skipping spring animation. Default: true. */
37
+ reducedMotion?: boolean;
38
+ /** Called when open animation completes. */
39
+ onOpen?: () => void;
40
+ /** Called when close animation completes. */
41
+ onClose?: () => void;
42
+ onSnap?: (point: number) => void;
43
+ }
44
+ /** Subset of SheetOptions that can be changed at runtime via setOptions(). */
45
+ type MutableSheetOptions = Pick<SheetOptions, "snapPoints" | "damping" | "stiffness" | "mass" | "closeThreshold" | "topPadding" | "width" | "align" | "sidePadding" | "maxWidth" | "ariaLabel" | "ariaLabelledBy" | "closeOnEscape" | "closeOnOverlayClick" | "modal" | "reducedMotion" | "onOpen" | "onClose" | "onSnap">;
46
+ declare class Sheet {
47
+ private container;
48
+ private options;
49
+ private wrapperEl;
50
+ private overlayEl;
51
+ private handleEl;
52
+ private contentEl;
53
+ private state;
54
+ private currentSnap;
55
+ private currentH;
56
+ private dragStartH;
57
+ private cancelAnimation;
58
+ private cleanupGestures;
59
+ private cleanupFocusTrap;
60
+ private unlockScroll;
61
+ private onPageShow;
62
+ private onResize;
63
+ private naturalContentH;
64
+ private destroyed;
65
+ private static sanitizeSnapPoints;
66
+ private prefersReducedMotion;
67
+ constructor(container: HTMLElement, options?: SheetOptions);
68
+ private getContentHeight;
69
+ private maxSheetH;
70
+ /** Convert a normalized snap point (0–1) to available viewport height. */
71
+ private snapToPixels;
72
+ /** Get all snap points as pixel heights, sorted ascending. */
73
+ private snapPixels;
74
+ /** Find the normalized snap point closest to a pixel height. */
75
+ private pixelsToSnap;
76
+ private applyLayout;
77
+ private buildDOM;
78
+ private bindGestures;
79
+ private cancelCurrentAnimation;
80
+ private animateToH;
81
+ private render;
82
+ /** Shared cleanup: visually close and tear down locks/traps. Does NOT fire callbacks. */
83
+ private resetToClosedState;
84
+ private onClosed;
85
+ private onKeyDown;
86
+ open(point?: number): void;
87
+ close(): void;
88
+ snapTo(point: number): void;
89
+ destroy(): void;
90
+ setSnapPoints(points: number[]): void;
91
+ /** Re-measure content height and recompute current position. Call after dynamic content changes. */
92
+ refresh(): void;
93
+ setOptions(opts: MutableSheetOptions): void;
94
+ setLayout(opts: {
95
+ width?: number;
96
+ align?: SheetAlign;
97
+ sidePadding?: number;
98
+ }): void;
99
+ get isOpen(): boolean;
100
+ get currentSnapPoint(): number;
101
+ }
102
+
103
+ interface SpringConfig {
104
+ damping: number;
105
+ stiffness: number;
106
+ mass: number;
107
+ restThreshold: number;
108
+ }
109
+
110
+ export { type DragMode, type MutableSheetOptions, Sheet, type SheetAlign, type SheetOptions, type SpringConfig };
@@ -0,0 +1,110 @@
1
+ type SheetAlign = "center" | "left" | "right";
2
+ type DragMode = "handle" | "sheet" | "none";
3
+ interface SheetOptions {
4
+ snapPoints?: number[];
5
+ defaultSnap?: number;
6
+ overlay?: boolean;
7
+ dragHandle?: boolean;
8
+ damping?: number;
9
+ stiffness?: number;
10
+ mass?: number;
11
+ closeThreshold?: number;
12
+ /** Top padding in px for the maximum open height. Default: 40. */
13
+ topPadding?: number;
14
+ /** Width as a percentage of the viewport (0–100). Default: 60 on desktop, 100 on mobile. */
15
+ width?: number;
16
+ /** Horizontal alignment. Default: "center". */
17
+ align?: SheetAlign;
18
+ /** Minimum side padding in px. Default: 0. */
19
+ sidePadding?: number;
20
+ /** Max width in px. 0 = no max. Default: 0. */
21
+ maxWidth?: number;
22
+ /** Which part of the sheet initiates drag. Default: "sheet". */
23
+ dragMode?: DragMode;
24
+ /** Accessible label for the sheet dialog. Default: "Sheet". */
25
+ ariaLabel?: string;
26
+ /** ID of the element labelling the sheet. Overrides ariaLabel when set. */
27
+ ariaLabelledBy?: string;
28
+ /** Close on Escape key. Default: true. */
29
+ closeOnEscape?: boolean;
30
+ /** Close on overlay click. Default: true. */
31
+ closeOnOverlayClick?: boolean;
32
+ /** Trap focus inside the sheet. Default: true. */
33
+ trapFocus?: boolean;
34
+ /** Set aria-modal. Default: true. */
35
+ modal?: boolean;
36
+ /** Honour prefers-reduced-motion by skipping spring animation. Default: true. */
37
+ reducedMotion?: boolean;
38
+ /** Called when open animation completes. */
39
+ onOpen?: () => void;
40
+ /** Called when close animation completes. */
41
+ onClose?: () => void;
42
+ onSnap?: (point: number) => void;
43
+ }
44
+ /** Subset of SheetOptions that can be changed at runtime via setOptions(). */
45
+ type MutableSheetOptions = Pick<SheetOptions, "snapPoints" | "damping" | "stiffness" | "mass" | "closeThreshold" | "topPadding" | "width" | "align" | "sidePadding" | "maxWidth" | "ariaLabel" | "ariaLabelledBy" | "closeOnEscape" | "closeOnOverlayClick" | "modal" | "reducedMotion" | "onOpen" | "onClose" | "onSnap">;
46
+ declare class Sheet {
47
+ private container;
48
+ private options;
49
+ private wrapperEl;
50
+ private overlayEl;
51
+ private handleEl;
52
+ private contentEl;
53
+ private state;
54
+ private currentSnap;
55
+ private currentH;
56
+ private dragStartH;
57
+ private cancelAnimation;
58
+ private cleanupGestures;
59
+ private cleanupFocusTrap;
60
+ private unlockScroll;
61
+ private onPageShow;
62
+ private onResize;
63
+ private naturalContentH;
64
+ private destroyed;
65
+ private static sanitizeSnapPoints;
66
+ private prefersReducedMotion;
67
+ constructor(container: HTMLElement, options?: SheetOptions);
68
+ private getContentHeight;
69
+ private maxSheetH;
70
+ /** Convert a normalized snap point (0–1) to available viewport height. */
71
+ private snapToPixels;
72
+ /** Get all snap points as pixel heights, sorted ascending. */
73
+ private snapPixels;
74
+ /** Find the normalized snap point closest to a pixel height. */
75
+ private pixelsToSnap;
76
+ private applyLayout;
77
+ private buildDOM;
78
+ private bindGestures;
79
+ private cancelCurrentAnimation;
80
+ private animateToH;
81
+ private render;
82
+ /** Shared cleanup: visually close and tear down locks/traps. Does NOT fire callbacks. */
83
+ private resetToClosedState;
84
+ private onClosed;
85
+ private onKeyDown;
86
+ open(point?: number): void;
87
+ close(): void;
88
+ snapTo(point: number): void;
89
+ destroy(): void;
90
+ setSnapPoints(points: number[]): void;
91
+ /** Re-measure content height and recompute current position. Call after dynamic content changes. */
92
+ refresh(): void;
93
+ setOptions(opts: MutableSheetOptions): void;
94
+ setLayout(opts: {
95
+ width?: number;
96
+ align?: SheetAlign;
97
+ sidePadding?: number;
98
+ }): void;
99
+ get isOpen(): boolean;
100
+ get currentSnapPoint(): number;
101
+ }
102
+
103
+ interface SpringConfig {
104
+ damping: number;
105
+ stiffness: number;
106
+ mass: number;
107
+ restThreshold: number;
108
+ }
109
+
110
+ export { type DragMode, type MutableSheetOptions, Sheet, type SheetAlign, type SheetOptions, type SpringConfig };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var w={damping:30,stiffness:200,mass:1,restThreshold:.5};function O(s,e,n,i,r){let{stiffness:t,damping:a,mass:d}=r,o=s-e,c=-t*o,u=-a*n,l=(c+u)/d,h=n+l*i;return{position:s+h*i,velocity:h}}function A(s,e,n,i=w.restThreshold){return Math.abs(s-e)<i&&Math.abs(n)<i}function L(s,e,n,i,r,t){let a={...w,...i},d=s,o=n,c=0,u=0;function l(h){if(c===0){c=h,u=requestAnimationFrame(l);return}let p=Math.min((h-c)/1e3,.064);c=h;let m=O(d,e,o,p,a);if(d=m.position,o=m.velocity,r(d),A(d,e,o,a.restThreshold)){r(e),t();return}u=requestAnimationFrame(l)}return u=requestAnimationFrame(l),()=>cancelAnimationFrame(u)}var g=class{constructor(e=8){this.samples=[];this.maxSamples=e}add(e){this.samples.push({time:performance.now(),value:e}),this.samples.length>this.maxSamples&&this.samples.shift()}getVelocity(){if(this.samples.length<2)return 0;let e=this.samples[this.samples.length-1],n=this.samples[0];for(let r=this.samples.length-2;r>=0;r--)if(e.time-this.samples[r].time>=30){n=this.samples[r];break}let i=(e.time-n.time)/1e3;return i===0?0:(e.value-n.value)/i}reset(){this.samples=[]}};function T(s,e,n,i=400,r=1200){if(n.length===0)return s;if(n.length===1)return n[0];let t=[...n].sort((o,c)=>o-c);if(Math.abs(e)>r)return e>0?t[t.length-1]:t[0];if(Math.abs(e)>i)if(e>0){for(let o of t)if(o>s+2)return o;return t[t.length-1]}else{for(let o=t.length-1;o>=0;o--)if(t[o]<s-2)return t[o];return t[0]}let a=t[0],d=Math.abs(s-t[0]);for(let o=1;o<t.length;o++){let c=Math.abs(s-t[o]);c<d&&(a=t[o],d=c)}return a}var P=6,k=3;function F(s,e){let n=s;for(;n&&n!==e;){if(n.scrollHeight>n.clientHeight){let r=getComputedStyle(n).overflowY;if(r==="auto"||r==="scroll")return n}n=n.parentElement}return null}function x(s,e,n,i){let r=new g,t={active:!1,dragging:!1,startY:0,currentY:0,directionLocked:!1,lockedAxis:null,startX:0,currentX:0,scrolling:!1,scrollTarget:null};function a(l){let h=e?.contains(l.target);if(n&&!h)return;let p=h?null:F(l.target,s);t.active=!0,t.dragging=!1,t.startY=l.clientY,t.currentY=l.clientY,t.startX=l.clientX,t.currentX=l.clientX,t.directionLocked=!1,t.lockedAxis=null,t.scrolling=!1,t.scrollTarget=p,r.reset(),r.add(l.clientY),s.setPointerCapture(l.pointerId)}function d(l){if(!t.active||t.scrolling)return;t.currentY=l.clientY,t.currentX=l.clientX;let h=Math.abs(t.currentX-t.startX),p=Math.abs(t.currentY-t.startY),m=t.currentY-t.startY;if(!(!t.directionLocked&&((h>P||p>P)&&(t.directionLocked=!0,t.lockedAxis=p>=h?"y":"x"),p<k))&&t.lockedAxis!=="x"){if(t.scrollTarget&&!t.dragging){let f=t.scrollTarget,M=f.scrollTop<=0,C=f.scrollTop+f.clientHeight>=f.scrollHeight-1,E=m>0;if(!(E&&M)){if(!(!E&&C)){t.scrolling=!0;return}}}t.dragging||(t.dragging=!0,i.onStart(t.startY)),l.preventDefault(),r.add(l.clientY),i.onMove(t.currentY,m)}}function o(l){if(!t.active||(t.active=!1,s.releasePointerCapture(l.pointerId),!t.dragging))return;t.dragging=!1;let h=r.getVelocity();i.onEnd(h)}function c(l){t.active&&(t.active=!1,s.releasePointerCapture(l.pointerId),t.dragging&&(t.dragging=!1,i.onEnd(0)))}s.addEventListener("pointerdown",a),s.addEventListener("pointermove",d),s.addEventListener("pointerup",o),s.addEventListener("pointercancel",c);let u=l=>{t.active&&l.preventDefault()};return s.addEventListener("contextmenu",u),()=>{s.removeEventListener("pointerdown",a),s.removeEventListener("pointermove",d),s.removeEventListener("pointerup",o),s.removeEventListener("pointercancel",c),s.removeEventListener("contextmenu",u)}}var D=["a[href]","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", ");function v(s){let e=null;function n(){return Array.from(s.querySelectorAll(D))}function i(a){if(a.key!=="Tab")return;let d=n();if(d.length===0){a.preventDefault();return}let o=d[0],c=d[d.length-1];a.shiftKey?(document.activeElement===o||!s.contains(document.activeElement))&&(a.preventDefault(),c.focus()):(document.activeElement===c||!s.contains(document.activeElement))&&(a.preventDefault(),o.focus())}function r(){e=document.activeElement,s.addEventListener("keydown",i),requestAnimationFrame(()=>{let a=n();a.length>0?a[0].focus():s.focus()})}function t(){s.removeEventListener("keydown",i),e?.focus(),e=null}return r(),t}function y(s,e,n={}){s.setAttribute("role","dialog"),s.setAttribute("aria-modal",n.modal!==!1?"true":"false"),n.ariaLabelledBy?(s.setAttribute("aria-labelledby",n.ariaLabelledBy),s.removeAttribute("aria-label")):(s.setAttribute("aria-label",n.ariaLabel||"Sheet"),s.removeAttribute("aria-labelledby")),e&&e.setAttribute("aria-hidden","true")}function H(s,e){s.removeAttribute("role"),s.removeAttribute("aria-modal"),e&&e.removeAttribute("aria-hidden")}function b(){let s=document.body,e=document.documentElement,n=s.style.overflow,i=e.style.overflow;e.style.overflow="hidden",s.style.overflow="hidden";let r=t=>{let a=t.target;for(;a;){if(a.classList?.contains("drwr-content"))return;a=a.parentElement}t.preventDefault()};return document.addEventListener("touchmove",r,{passive:!1}),()=>{s.style.overflow=n,e.style.overflow=i,document.removeEventListener("touchmove",r)}}function Y(s){return 8*(Math.log(s+1)-2)}var S=class s{constructor(e,n={}){this.overlayEl=null;this.handleEl=null;this.state="closed";this.currentSnap=0;this.currentH=0;this.dragStartH=0;this.cancelAnimation=null;this.cleanupGestures=null;this.cleanupFocusTrap=null;this.unlockScroll=null;this.onPageShow=null;this.onResize=null;this.naturalContentH=0;this.destroyed=!1;this.onKeyDown=e=>{e.key==="Escape"&&this.state!=="closed"&&this.options.closeOnEscape&&this.close()};this.container=e;let i=s.sanitizeSnapPoints(n.snapPoints??[0,1]);this.options={snapPoints:i,defaultSnap:n.defaultSnap??0,overlay:n.overlay??!0,dragHandle:n.dragHandle??!0,damping:n.damping??30,stiffness:n.stiffness??200,mass:n.mass??1,closeThreshold:n.closeThreshold??.2,topPadding:n.topPadding??40,width:n.width??(window.innerWidth>=768?60:100),align:n.align??"center",sidePadding:n.sidePadding??0,maxWidth:n.maxWidth??0,dragMode:n.dragMode??"sheet",ariaLabel:n.ariaLabel??"Sheet",ariaLabelledBy:n.ariaLabelledBy??"",closeOnEscape:n.closeOnEscape??!0,closeOnOverlayClick:n.closeOnOverlayClick??!0,trapFocus:n.trapFocus??!0,modal:n.modal??!0,reducedMotion:n.reducedMotion??!0,onOpen:n.onOpen??(()=>{}),onClose:n.onClose??(()=>{}),onSnap:n.onSnap??(()=>{})},this.buildDOM(),this.container.removeAttribute("hidden"),this.naturalContentH=this.getContentHeight(),this.bindGestures();let r=this.options.defaultSnap;r>0&&this.options.snapPoints.includes(r)?(this.currentH=this.snapToPixels(r),this.render(this.currentH),this.state="open",this.currentSnap=r,document.addEventListener("keydown",this.onKeyDown),this.unlockScroll||(this.unlockScroll=b()),this.options.trapFocus&&!this.cleanupFocusTrap&&(this.cleanupFocusTrap=v(this.wrapperEl)),queueMicrotask(()=>{this.destroyed||this.options.onOpen()})):(this.currentH=0,this.render(0)),this.onPageShow=t=>{t.persisted&&this.state!=="closed"&&this.resetToClosedState()},window.addEventListener("pageshow",this.onPageShow),this.onResize=()=>{this.applyLayout(),this.naturalContentH=this.getContentHeight(),this.state!=="closed"&&this.currentSnap>0&&(this.currentH=this.snapToPixels(this.currentSnap),this.render(this.currentH))},window.addEventListener("resize",this.onResize)}static sanitizeSnapPoints(e){let n=e.filter(r=>Number.isFinite(r)&&r>=0&&r<=1),i=[...new Set(n)].sort((r,t)=>r-t);return i.length===0?[0,1]:(i.includes(0)||i.unshift(0),i)}prefersReducedMotion(){return this.options.reducedMotion&&typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches}getContentHeight(){return this.contentEl.scrollHeight+(this.handleEl?.offsetHeight??0)}maxSheetH(){return window.innerHeight-this.options.topPadding}snapToPixels(e){if(e<=0)return 0;let n=this.maxSheetH();return Math.min(e,1)*n}snapPixels(){return this.options.snapPoints.map(e=>this.snapToPixels(e)).sort((e,n)=>e-n)}pixelsToSnap(e){let n=this.options.snapPoints,i=n[0],r=Math.abs(e-this.snapToPixels(n[0]));for(let t=1;t<n.length;t++){let a=Math.abs(e-this.snapToPixels(n[t]));a<r&&(i=n[t],r=a)}return i}applyLayout(){let{width:e,align:n,sidePadding:i,maxWidth:r}=this.options,t=this.wrapperEl,a=window.innerWidth,d=Math.max(10,Math.min(100,e)),o=Math.max(0,Math.min(i,a/2-10)),c=Math.max(20,Math.min(a*(d/100),a-o*2));r>0&&(c=Math.min(c,r));let u;n==="left"?u=o:n==="right"?u=a-c-o:u=(a-c)/2,t.style.left=`${u}px`,t.style.right="auto",t.style.width=`${c}px`,t.style.margin="0"}buildDOM(){for(this.wrapperEl=document.createElement("div"),this.wrapperEl.className="drwr-sheet",this.wrapperEl.setAttribute("tabindex","-1"),this.options.dragHandle&&(this.handleEl=document.createElement("div"),this.handleEl.className="drwr-handle",this.wrapperEl.appendChild(this.handleEl)),this.contentEl=document.createElement("div"),this.contentEl.className="drwr-content";this.container.firstChild;)this.contentEl.appendChild(this.container.firstChild);this.wrapperEl.appendChild(this.contentEl),this.container.appendChild(this.wrapperEl),this.options.overlay&&(this.overlayEl=document.createElement("div"),this.overlayEl.className="drwr-overlay",this.overlayEl.addEventListener("click",()=>{this.options.closeOnOverlayClick&&this.close()}),this.container.insertBefore(this.overlayEl,this.wrapperEl)),y(this.wrapperEl,this.overlayEl,{ariaLabel:this.options.ariaLabel,ariaLabelledBy:this.options.ariaLabelledBy,modal:this.options.modal}),this.applyLayout()}bindGestures(){this.options.dragMode!=="none"&&(this.cleanupGestures=x(this.wrapperEl,this.handleEl,this.options.dragMode==="handle",{onStart:()=>{this.cancelCurrentAnimation(),this.state="dragging",this.dragStartH=this.currentH,this.wrapperEl.setAttribute("data-dragging","true")},onMove:(e,n)=>{let i=this.dragStartH-n,r=this.snapPixels(),t=r[r.length-1],a=r[0];if(i>t)i=t;else if(i<a){let d=a-i;i=a-Math.max(0,Y(d))}this.currentH=i,this.render(i)},onEnd:e=>{this.wrapperEl.setAttribute("data-dragging","false");let n=-e;this.state="animating";let i=this.snapPixels(),r=T(this.currentH,n,i,400),t=i.filter(a=>a>0);if(t.length>0){let a=t[0];this.currentH<a*this.options.closeThreshold&&(r=0)}this.animateToH(r,n)}}))}cancelCurrentAnimation(){this.cancelAnimation&&(this.cancelAnimation(),this.cancelAnimation=null)}animateToH(e,n=0){let i=this.state==="open"||this.state==="dragging";this.cancelCurrentAnimation(),this.state="animating";let r=()=>{this.currentH=e,this.render(e),this.cancelAnimation=null;let a=this.pixelsToSnap(e);this.currentSnap=a,e<=.5?this.onClosed():(this.state="open",i||this.options.onOpen(),this.options.onSnap(a))};if(this.prefersReducedMotion()){r();return}let t={damping:this.options.damping,stiffness:this.options.stiffness,mass:this.options.mass};this.cancelAnimation=L(this.currentH,e,n,t,a=>{this.currentH=a,this.render(a)},r)}render(e){let n=window.innerHeight,i=Math.max(0,e),r=n-i;if(this.wrapperEl.style.visibility=i>.5?"visible":"hidden",this.wrapperEl.style.transform=`translate3d(0, ${r}px, 0)`,this.wrapperEl.style.height=i>.5?`${i}px`:"",this.overlayEl){let t=this.snapPixels(),a=t[t.length-1],d=Math.max(0,Math.min(1,i/(a*.3)));this.overlayEl.style.opacity=String(d),this.overlayEl.setAttribute("data-visible",i>1?"true":"false")}}resetToClosedState(){this.cancelCurrentAnimation(),this.currentH=0,this.currentSnap=0,this.render(0),this.state="closed",this.overlayEl&&(this.overlayEl.style.opacity="0",this.overlayEl.setAttribute("data-visible","false")),this.unlockScroll&&(this.unlockScroll(),this.unlockScroll=null),this.cleanupFocusTrap&&(this.cleanupFocusTrap(),this.cleanupFocusTrap=null),document.removeEventListener("keydown",this.onKeyDown)}onClosed(){this.resetToClosedState(),this.options.onClose()}open(e){if(this.destroyed||this.state==="open")return;let n=[...this.options.snapPoints].sort((r,t)=>r-t),i;if(e!==void 0){if(!n.includes(e)||e===0){console.warn(`[drwr] snap point ${e} not valid for open()`);return}i=e}else i=n[n.length-1];document.addEventListener("keydown",this.onKeyDown),this.animateToH(this.snapToPixels(i)),this.unlockScroll||(this.unlockScroll=b()),this.options.trapFocus&&!this.cleanupFocusTrap&&(this.cleanupFocusTrap=v(this.wrapperEl))}close(){this.destroyed||this.state==="closed"||(document.removeEventListener("keydown",this.onKeyDown),this.animateToH(0))}snapTo(e){if(!this.destroyed){if(!this.options.snapPoints.includes(e)){console.warn(`[drwr] snap point ${e} not in snapPoints`);return}e===0?this.close():(this.state==="closed"&&(document.addEventListener("keydown",this.onKeyDown),this.unlockScroll||(this.unlockScroll=b()),this.options.trapFocus&&!this.cleanupFocusTrap&&(this.cleanupFocusTrap=v(this.wrapperEl))),this.animateToH(this.snapToPixels(e)))}}destroy(){if(!this.destroyed){for(this.destroyed=!0,this.cancelCurrentAnimation(),document.removeEventListener("keydown",this.onKeyDown),this.cleanupGestures&&(this.cleanupGestures(),this.cleanupGestures=null),this.cleanupFocusTrap&&(this.cleanupFocusTrap(),this.cleanupFocusTrap=null),this.unlockScroll&&(this.unlockScroll(),this.unlockScroll=null),this.onPageShow&&(window.removeEventListener("pageshow",this.onPageShow),this.onPageShow=null),this.onResize&&(window.removeEventListener("resize",this.onResize),this.onResize=null),H(this.wrapperEl,this.overlayEl);this.contentEl.firstChild;)this.container.appendChild(this.contentEl.firstChild);this.wrapperEl.remove(),this.overlayEl?.remove()}}setSnapPoints(e){if(!this.destroyed&&(this.options.snapPoints=s.sanitizeSnapPoints(e),this.state!=="closed"&&this.currentSnap>0&&!this.options.snapPoints.includes(this.currentSnap))){let n=this.pixelsToSnap(this.currentH);this.animateToH(this.snapToPixels(n))}}refresh(){this.destroyed||(this.naturalContentH=this.getContentHeight(),this.state!=="closed"&&this.currentSnap>0&&(this.currentH=this.snapToPixels(this.currentSnap),this.render(this.currentH)))}setOptions(e){if(this.destroyed)return;if(e.snapPoints!==void 0&&(this.options.snapPoints=s.sanitizeSnapPoints(e.snapPoints)),e.damping!==void 0&&(this.options.damping=e.damping),e.stiffness!==void 0&&(this.options.stiffness=e.stiffness),e.mass!==void 0&&(this.options.mass=e.mass),e.closeThreshold!==void 0&&(this.options.closeThreshold=e.closeThreshold),e.topPadding!==void 0&&(this.options.topPadding=e.topPadding),e.closeOnEscape!==void 0&&(this.options.closeOnEscape=e.closeOnEscape),e.closeOnOverlayClick!==void 0&&(this.options.closeOnOverlayClick=e.closeOnOverlayClick),e.ariaLabel!==void 0&&(this.options.ariaLabel=e.ariaLabel),e.ariaLabelledBy!==void 0&&(this.options.ariaLabelledBy=e.ariaLabelledBy),e.modal!==void 0&&(this.options.modal=e.modal),e.maxWidth!==void 0&&(this.options.maxWidth=e.maxWidth),e.reducedMotion!==void 0&&(this.options.reducedMotion=e.reducedMotion),e.onOpen!==void 0&&(this.options.onOpen=e.onOpen),e.onClose!==void 0&&(this.options.onClose=e.onClose),e.onSnap!==void 0&&(this.options.onSnap=e.onSnap),(e.ariaLabel!==void 0||e.ariaLabelledBy!==void 0||e.modal!==void 0)&&y(this.wrapperEl,this.overlayEl,{ariaLabel:this.options.ariaLabel,ariaLabelledBy:this.options.ariaLabelledBy,modal:this.options.modal}),(e.width!==void 0||e.align!==void 0||e.sidePadding!==void 0||e.maxWidth!==void 0)&&(e.width!==void 0&&(this.options.width=e.width),e.align!==void 0&&(this.options.align=e.align),e.sidePadding!==void 0&&(this.options.sidePadding=e.sidePadding),this.applyLayout()),(e.topPadding!==void 0||e.snapPoints!==void 0)&&this.state!=="closed"&&this.currentSnap>0)if(e.snapPoints!==void 0&&!this.options.snapPoints.includes(this.currentSnap)){let i=this.pixelsToSnap(this.currentH);this.animateToH(this.snapToPixels(i))}else this.currentH=this.snapToPixels(this.currentSnap),this.render(this.currentH)}setLayout(e){this.destroyed||(e.width!==void 0&&(this.options.width=e.width),e.align!==void 0&&(this.options.align=e.align),e.sidePadding!==void 0&&(this.options.sidePadding=e.sidePadding),this.applyLayout())}get isOpen(){return this.state!=="closed"}get currentSnapPoint(){return this.currentSnap}};export{S as Sheet};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/physics.ts","../src/gestures.ts","../src/accessibility.ts","../src/sheet.ts"],"sourcesContent":["export interface SpringConfig {\n damping: number;\n stiffness: number;\n mass: number;\n restThreshold: number;\n}\n\nexport const DEFAULT_SPRING: SpringConfig = {\n damping: 30,\n stiffness: 200,\n mass: 1,\n restThreshold: 0.5,\n};\n\nexport interface SpringState {\n position: number;\n velocity: number;\n}\n\n/**\n * Solve one step of a damped spring using semi-implicit Euler integration.\n * Returns the new position and velocity after `dt` seconds.\n */\nexport function springStep(\n current: number,\n target: number,\n velocity: number,\n dt: number,\n config: SpringConfig,\n): SpringState {\n const { stiffness, damping, mass } = config;\n const displacement = current - target;\n const springForce = -stiffness * displacement;\n const dampingForce = -damping * velocity;\n const acceleration = (springForce + dampingForce) / mass;\n\n const newVelocity = velocity + acceleration * dt;\n const newPosition = current + newVelocity * dt;\n\n return { position: newPosition, velocity: newVelocity };\n}\n\n/**\n * Returns true when the spring is close enough to rest.\n */\nexport function isAtRest(\n current: number,\n target: number,\n velocity: number,\n threshold: number = DEFAULT_SPRING.restThreshold,\n): boolean {\n return (\n Math.abs(current - target) < threshold && Math.abs(velocity) < threshold\n );\n}\n\n/**\n * Runs a spring animation via requestAnimationFrame. Returns a cancel function.\n */\nexport function animateSpring(\n from: number,\n to: number,\n initialVelocity: number,\n config: Partial<SpringConfig>,\n onUpdate: (value: number) => void,\n onComplete: () => void,\n): () => void {\n const cfg = { ...DEFAULT_SPRING, ...config };\n let position = from;\n let velocity = initialVelocity;\n let lastTime = 0;\n let frameId = 0;\n\n function tick(time: number) {\n if (lastTime === 0) {\n lastTime = time;\n frameId = requestAnimationFrame(tick);\n return;\n }\n\n // Cap dt to avoid spiral of death on tab-switch resume\n const dt = Math.min((time - lastTime) / 1000, 0.064);\n lastTime = time;\n\n const state = springStep(position, to, velocity, dt, cfg);\n position = state.position;\n velocity = state.velocity;\n\n onUpdate(position);\n\n if (isAtRest(position, to, velocity, cfg.restThreshold)) {\n onUpdate(to);\n onComplete();\n return;\n }\n\n frameId = requestAnimationFrame(tick);\n }\n\n frameId = requestAnimationFrame(tick);\n\n return () => cancelAnimationFrame(frameId);\n}\n\n/**\n * Tracks pointer velocity over the last N samples for flick detection.\n */\nexport class VelocityTracker {\n private samples: { time: number; value: number }[] = [];\n private maxSamples: number;\n\n constructor(maxSamples = 8) {\n this.maxSamples = maxSamples;\n }\n\n add(value: number) {\n this.samples.push({ time: performance.now(), value });\n if (this.samples.length > this.maxSamples) {\n this.samples.shift();\n }\n }\n\n /** Returns velocity in units per second. */\n getVelocity(): number {\n if (this.samples.length < 2) return 0;\n\n const recent = this.samples[this.samples.length - 1];\n // Use sample from ~50ms ago for stability\n let oldest = this.samples[0];\n for (let i = this.samples.length - 2; i >= 0; i--) {\n if (recent.time - this.samples[i].time >= 30) {\n oldest = this.samples[i];\n break;\n }\n }\n\n const dt = (recent.time - oldest.time) / 1000;\n if (dt === 0) return 0;\n\n return (recent.value - oldest.value) / dt;\n }\n\n reset() {\n this.samples = [];\n }\n}\n\n/**\n * Given a set of snap points and a release position + velocity,\n * determine which snap point to target.\n */\nexport function resolveSnapPoint(\n position: number,\n velocity: number,\n snapPoints: number[],\n flickThreshold = 400,\n hardFlickThreshold = 1200,\n): number {\n if (snapPoints.length === 0) return position;\n if (snapPoints.length === 1) return snapPoints[0];\n\n const sorted = [...snapPoints].sort((a, b) => a - b);\n\n // Hard flick: skip all intermediate snaps, go to the extreme\n if (Math.abs(velocity) > hardFlickThreshold) {\n return velocity > 0 ? sorted[sorted.length - 1] : sorted[0];\n }\n\n // Normal flick: go to next snap in the flick direction\n if (Math.abs(velocity) > flickThreshold) {\n if (velocity > 0) {\n for (const sp of sorted) {\n if (sp > position + 2) return sp;\n }\n return sorted[sorted.length - 1];\n } else {\n for (let i = sorted.length - 1; i >= 0; i--) {\n if (sorted[i] < position - 2) return sorted[i];\n }\n return sorted[0];\n }\n }\n\n // No flick: snap to nearest\n let closest = sorted[0];\n let closestDist = Math.abs(position - sorted[0]);\n for (let i = 1; i < sorted.length; i++) {\n const dist = Math.abs(position - sorted[i]);\n if (dist < closestDist) {\n closest = sorted[i];\n closestDist = dist;\n }\n }\n return closest;\n}\n","import { VelocityTracker } from \"./physics.js\";\n\nexport interface GestureCallbacks {\n onStart: (y: number) => void;\n onMove: (y: number, deltaY: number) => void;\n onEnd: (velocity: number) => void;\n}\n\ninterface GestureState {\n active: boolean;\n dragging: boolean;\n startY: number;\n currentY: number;\n directionLocked: boolean;\n lockedAxis: \"x\" | \"y\" | null;\n startX: number;\n currentX: number;\n scrolling: boolean;\n scrollTarget: HTMLElement | null;\n}\n\nconst DIRECTION_LOCK_THRESHOLD = 6;\nconst SCROLL_LOCK_THRESHOLD = 3;\n\nfunction findScrollableAncestor(\n target: EventTarget | null,\n sheetEl: HTMLElement,\n): HTMLElement | null {\n let el = target as HTMLElement | null;\n while (el && el !== sheetEl) {\n if (el.scrollHeight > el.clientHeight) {\n const style = getComputedStyle(el);\n const overflowY = style.overflowY;\n if (overflowY === \"auto\" || overflowY === \"scroll\") {\n return el;\n }\n }\n el = el.parentElement;\n }\n return null;\n}\n\nexport function attachGestures(\n sheetEl: HTMLElement,\n handleEl: HTMLElement | null,\n handleOnly: boolean,\n callbacks: GestureCallbacks,\n): () => void {\n const tracker = new VelocityTracker();\n const state: GestureState = {\n active: false,\n dragging: false,\n startY: 0,\n currentY: 0,\n directionLocked: false,\n lockedAxis: null,\n startX: 0,\n currentX: 0,\n scrolling: false,\n scrollTarget: null,\n };\n\n function onPointerDown(e: PointerEvent) {\n const isHandle = handleEl?.contains(e.target as Node);\n if (handleOnly && !isHandle) return;\n const scrollable = isHandle\n ? null\n : findScrollableAncestor(e.target, sheetEl);\n\n state.active = true;\n state.dragging = false;\n state.startY = e.clientY;\n state.currentY = e.clientY;\n state.startX = e.clientX;\n state.currentX = e.clientX;\n state.directionLocked = false;\n state.lockedAxis = null;\n state.scrolling = false;\n state.scrollTarget = scrollable;\n\n tracker.reset();\n tracker.add(e.clientY);\n\n sheetEl.setPointerCapture(e.pointerId);\n }\n\n function onPointerMove(e: PointerEvent) {\n if (!state.active || state.scrolling) return;\n\n state.currentY = e.clientY;\n state.currentX = e.clientX;\n\n const dx = Math.abs(state.currentX - state.startX);\n const dy = Math.abs(state.currentY - state.startY);\n const deltaY = state.currentY - state.startY;\n\n // Direction locking\n if (!state.directionLocked) {\n if (dx > DIRECTION_LOCK_THRESHOLD || dy > DIRECTION_LOCK_THRESHOLD) {\n state.directionLocked = true;\n state.lockedAxis = dy >= dx ? \"y\" : \"x\";\n }\n if (dy < SCROLL_LOCK_THRESHOLD) return;\n }\n\n if (state.lockedAxis === \"x\") return;\n\n // Scroll vs drag disambiguation (decided once)\n if (state.scrollTarget && !state.dragging) {\n const st = state.scrollTarget;\n const atTop = st.scrollTop <= 0;\n const atBottom = st.scrollTop + st.clientHeight >= st.scrollHeight - 1;\n const draggingDown = deltaY > 0;\n\n if (draggingDown && atTop) {\n // At top, pulling down → drag sheet\n } else if (!draggingDown && atBottom) {\n // At bottom, pulling up → drag sheet\n } else {\n // Let the content scroll\n state.scrolling = true;\n return;\n }\n }\n\n // Begin drag on first qualifying move\n if (!state.dragging) {\n state.dragging = true;\n callbacks.onStart(state.startY);\n }\n\n e.preventDefault();\n tracker.add(e.clientY);\n callbacks.onMove(state.currentY, deltaY);\n }\n\n function onPointerUp(e: PointerEvent) {\n if (!state.active) return;\n state.active = false;\n sheetEl.releasePointerCapture(e.pointerId);\n\n if (!state.dragging) return;\n state.dragging = false;\n\n const velocity = tracker.getVelocity();\n callbacks.onEnd(velocity);\n }\n\n function onPointerCancel(e: PointerEvent) {\n if (!state.active) return;\n state.active = false;\n sheetEl.releasePointerCapture(e.pointerId);\n\n if (!state.dragging) return;\n state.dragging = false;\n callbacks.onEnd(0);\n }\n\n sheetEl.addEventListener(\"pointerdown\", onPointerDown);\n sheetEl.addEventListener(\"pointermove\", onPointerMove);\n sheetEl.addEventListener(\"pointerup\", onPointerUp);\n sheetEl.addEventListener(\"pointercancel\", onPointerCancel);\n\n const onContextMenu = (e: Event) => {\n if (state.active) e.preventDefault();\n };\n sheetEl.addEventListener(\"contextmenu\", onContextMenu);\n\n return () => {\n sheetEl.removeEventListener(\"pointerdown\", onPointerDown);\n sheetEl.removeEventListener(\"pointermove\", onPointerMove);\n sheetEl.removeEventListener(\"pointerup\", onPointerUp);\n sheetEl.removeEventListener(\"pointercancel\", onPointerCancel);\n sheetEl.removeEventListener(\"contextmenu\", onContextMenu);\n };\n}\n","const FOCUSABLE_SELECTORS = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(\", \");\n\n/**\n * Creates a focus trap within the given element.\n * Returns a cleanup function.\n */\nexport function createFocusTrap(container: HTMLElement): () => void {\n let previousFocus: HTMLElement | null = null;\n\n function getFocusable(): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS));\n }\n\n function onKeyDown(e: KeyboardEvent) {\n if (e.key !== \"Tab\") return;\n\n const focusable = getFocusable();\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first || !container.contains(document.activeElement)) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last || !container.contains(document.activeElement)) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n\n function activate() {\n previousFocus = document.activeElement as HTMLElement | null;\n container.addEventListener(\"keydown\", onKeyDown);\n\n // Focus first focusable element or the container itself\n requestAnimationFrame(() => {\n const focusable = getFocusable();\n if (focusable.length > 0) {\n focusable[0].focus();\n } else {\n container.focus();\n }\n });\n }\n\n function deactivate() {\n container.removeEventListener(\"keydown\", onKeyDown);\n previousFocus?.focus();\n previousFocus = null;\n }\n\n activate();\n\n return deactivate;\n}\n\nexport interface AriaOptions {\n ariaLabel?: string;\n ariaLabelledBy?: string;\n modal?: boolean;\n}\n\n/**\n * Applies ARIA attributes for a modal dialog.\n */\nexport function applyAriaAttributes(\n sheetEl: HTMLElement,\n overlayEl: HTMLElement | null,\n opts: AriaOptions = {},\n) {\n sheetEl.setAttribute(\"role\", \"dialog\");\n sheetEl.setAttribute(\"aria-modal\", opts.modal !== false ? \"true\" : \"false\");\n\n if (opts.ariaLabelledBy) {\n sheetEl.setAttribute(\"aria-labelledby\", opts.ariaLabelledBy);\n sheetEl.removeAttribute(\"aria-label\");\n } else {\n sheetEl.setAttribute(\"aria-label\", opts.ariaLabel || \"Sheet\");\n sheetEl.removeAttribute(\"aria-labelledby\");\n }\n\n if (overlayEl) {\n overlayEl.setAttribute(\"aria-hidden\", \"true\");\n }\n}\n\n/**\n * Removes ARIA attributes when the sheet is destroyed.\n */\nexport function removeAriaAttributes(\n sheetEl: HTMLElement,\n overlayEl: HTMLElement | null,\n) {\n sheetEl.removeAttribute(\"role\");\n sheetEl.removeAttribute(\"aria-modal\");\n if (overlayEl) {\n overlayEl.removeAttribute(\"aria-hidden\");\n }\n}\n\n/**\n * iOS-safe scroll lock. Prevents background scrolling without position:fixed\n * to avoid the iOS Safari viewport jump when the address bar is visible.\n */\nexport function lockScroll(): () => void {\n const body = document.body;\n const html = document.documentElement;\n\n const originalBodyOverflow = body.style.overflow;\n const originalHtmlOverflow = html.style.overflow;\n\n html.style.overflow = \"hidden\";\n body.style.overflow = \"hidden\";\n\n // On iOS Safari, overflow:hidden on body/html doesn't fully prevent\n // scroll on its own. We block touchmove on the document as a fallback.\n const preventTouch = (e: TouchEvent) => {\n // Allow scrolling inside the sheet content\n let el = e.target as HTMLElement | null;\n while (el) {\n if (el.classList?.contains(\"drwr-content\")) return;\n el = el.parentElement;\n }\n e.preventDefault();\n };\n\n document.addEventListener(\"touchmove\", preventTouch, { passive: false });\n\n return () => {\n body.style.overflow = originalBodyOverflow;\n html.style.overflow = originalHtmlOverflow;\n document.removeEventListener(\"touchmove\", preventTouch);\n };\n}\n","import {\n animateSpring,\n resolveSnapPoint,\n type SpringConfig,\n} from \"./physics.js\";\nimport { attachGestures } from \"./gestures.js\";\nimport {\n applyAriaAttributes,\n removeAriaAttributes,\n createFocusTrap,\n lockScroll,\n} from \"./accessibility.js\";\n\nexport type SheetAlign = \"center\" | \"left\" | \"right\";\nexport type DragMode = \"handle\" | \"sheet\" | \"none\";\n\nexport interface SheetOptions {\n snapPoints?: number[];\n defaultSnap?: number;\n overlay?: boolean;\n dragHandle?: boolean;\n damping?: number;\n stiffness?: number;\n mass?: number;\n closeThreshold?: number;\n /** Top padding in px for the maximum open height. Default: 40. */\n topPadding?: number;\n /** Width as a percentage of the viewport (0–100). Default: 60 on desktop, 100 on mobile. */\n width?: number;\n /** Horizontal alignment. Default: \"center\". */\n align?: SheetAlign;\n /** Minimum side padding in px. Default: 0. */\n sidePadding?: number;\n /** Max width in px. 0 = no max. Default: 0. */\n maxWidth?: number;\n /** Which part of the sheet initiates drag. Default: \"sheet\". */\n dragMode?: DragMode;\n /** Accessible label for the sheet dialog. Default: \"Sheet\". */\n ariaLabel?: string;\n /** ID of the element labelling the sheet. Overrides ariaLabel when set. */\n ariaLabelledBy?: string;\n /** Close on Escape key. Default: true. */\n closeOnEscape?: boolean;\n /** Close on overlay click. Default: true. */\n closeOnOverlayClick?: boolean;\n /** Trap focus inside the sheet. Default: true. */\n trapFocus?: boolean;\n /** Set aria-modal. Default: true. */\n modal?: boolean;\n /** Honour prefers-reduced-motion by skipping spring animation. Default: true. */\n reducedMotion?: boolean;\n /** Called when open animation completes. */\n onOpen?: () => void;\n /** Called when close animation completes. */\n onClose?: () => void;\n onSnap?: (point: number) => void;\n}\n\n/** Subset of SheetOptions that can be changed at runtime via setOptions(). */\nexport type MutableSheetOptions = Pick<\n SheetOptions,\n | \"snapPoints\"\n | \"damping\"\n | \"stiffness\"\n | \"mass\"\n | \"closeThreshold\"\n | \"topPadding\"\n | \"width\"\n | \"align\"\n | \"sidePadding\"\n | \"maxWidth\"\n | \"ariaLabel\"\n | \"ariaLabelledBy\"\n | \"closeOnEscape\"\n | \"closeOnOverlayClick\"\n | \"modal\"\n | \"reducedMotion\"\n | \"onOpen\"\n | \"onClose\"\n | \"onSnap\"\n>;\n\ntype SheetState = \"closed\" | \"open\" | \"dragging\" | \"animating\";\n\n/** Logarithmic resistance like vaul: soft, organic feel past bounds. */\nfunction dampen(v: number): number {\n return 8 * (Math.log(v + 1) - 2);\n}\n\nexport class Sheet {\n private container: HTMLElement;\n private options: Required<SheetOptions>;\n\n private wrapperEl!: HTMLElement;\n private overlayEl: HTMLElement | null = null;\n private handleEl: HTMLElement | null = null;\n private contentEl!: HTMLElement;\n\n private state: SheetState = \"closed\";\n private currentSnap = 0;\n // Current height of the sheet in pixels (0 = closed, positive = visible)\n private currentH = 0;\n private dragStartH = 0;\n\n private cancelAnimation: (() => void) | null = null;\n private cleanupGestures: (() => void) | null = null;\n private cleanupFocusTrap: (() => void) | null = null;\n private unlockScroll: (() => void) | null = null;\n private onPageShow: ((e: PageTransitionEvent) => void) | null = null;\n private onResize: (() => void) | null = null;\n private naturalContentH = 0;\n private destroyed = false;\n\n private static sanitizeSnapPoints(raw: number[]): number[] {\n const valid = raw.filter((n) => Number.isFinite(n) && n >= 0 && n <= 1);\n const deduped = [...new Set(valid)].sort((a, b) => a - b);\n if (deduped.length === 0) return [0, 1];\n if (!deduped.includes(0)) deduped.unshift(0);\n return deduped;\n }\n\n private prefersReducedMotion(): boolean {\n return (\n this.options.reducedMotion &&\n typeof window !== \"undefined\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n );\n }\n\n constructor(container: HTMLElement, options: SheetOptions = {}) {\n this.container = container;\n const snaps = Sheet.sanitizeSnapPoints(options.snapPoints ?? [0, 1]);\n this.options = {\n snapPoints: snaps,\n defaultSnap: options.defaultSnap ?? 0,\n overlay: options.overlay ?? true,\n dragHandle: options.dragHandle ?? true,\n damping: options.damping ?? 30,\n stiffness: options.stiffness ?? 200,\n mass: options.mass ?? 1,\n closeThreshold: options.closeThreshold ?? 0.2,\n topPadding: options.topPadding ?? 40,\n width: options.width ?? (window.innerWidth >= 768 ? 60 : 100),\n align: options.align ?? \"center\",\n sidePadding: options.sidePadding ?? 0,\n maxWidth: options.maxWidth ?? 0,\n dragMode: options.dragMode ?? \"sheet\",\n ariaLabel: options.ariaLabel ?? \"Sheet\",\n ariaLabelledBy: options.ariaLabelledBy ?? \"\",\n closeOnEscape: options.closeOnEscape ?? true,\n closeOnOverlayClick: options.closeOnOverlayClick ?? true,\n trapFocus: options.trapFocus ?? true,\n modal: options.modal ?? true,\n reducedMotion: options.reducedMotion ?? true,\n onOpen: options.onOpen ?? (() => {}),\n onClose: options.onClose ?? (() => {}),\n onSnap: options.onSnap ?? (() => {}),\n };\n\n this.buildDOM();\n this.container.removeAttribute(\"hidden\");\n this.naturalContentH = this.getContentHeight();\n this.bindGestures();\n\n const defaultSnap = this.options.defaultSnap;\n if (defaultSnap > 0 && this.options.snapPoints.includes(defaultSnap)) {\n this.currentH = this.snapToPixels(defaultSnap);\n this.render(this.currentH);\n this.state = \"open\";\n this.currentSnap = defaultSnap;\n document.addEventListener(\"keydown\", this.onKeyDown);\n if (!this.unlockScroll) this.unlockScroll = lockScroll();\n if (this.options.trapFocus && !this.cleanupFocusTrap) {\n this.cleanupFocusTrap = createFocusTrap(this.wrapperEl);\n }\n queueMicrotask(() => {\n if (this.destroyed) return;\n this.options.onOpen();\n });\n } else {\n this.currentH = 0;\n this.render(0);\n }\n this.onPageShow = (e: PageTransitionEvent) => {\n if (e.persisted && this.state !== \"closed\") {\n this.resetToClosedState();\n }\n };\n window.addEventListener(\"pageshow\", this.onPageShow);\n\n this.onResize = () => {\n this.applyLayout();\n this.naturalContentH = this.getContentHeight();\n if (this.state !== \"closed\" && this.currentSnap > 0) {\n this.currentH = this.snapToPixels(this.currentSnap);\n this.render(this.currentH);\n }\n };\n window.addEventListener(\"resize\", this.onResize);\n }\n\n // --- Snap point conversion ---\n\n private getContentHeight(): number {\n return this.contentEl.scrollHeight + (this.handleEl?.offsetHeight ?? 0);\n }\n\n private maxSheetH(): number {\n return window.innerHeight - this.options.topPadding;\n }\n\n /** Convert a normalized snap point (0–1) to available viewport height. */\n private snapToPixels(snap: number): number {\n if (snap <= 0) return 0;\n const maxH = this.maxSheetH();\n return Math.min(snap, 1) * maxH;\n }\n\n /** Get all snap points as pixel heights, sorted ascending. */\n private snapPixels(): number[] {\n return this.options.snapPoints\n .map((s) => this.snapToPixels(s))\n .sort((a, b) => a - b);\n }\n\n /** Find the normalized snap point closest to a pixel height. */\n private pixelsToSnap(px: number): number {\n const snaps = this.options.snapPoints;\n let closest = snaps[0];\n let closestDist = Math.abs(px - this.snapToPixels(snaps[0]));\n for (let i = 1; i < snaps.length; i++) {\n const dist = Math.abs(px - this.snapToPixels(snaps[i]));\n if (dist < closestDist) {\n closest = snaps[i];\n closestDist = dist;\n }\n }\n return closest;\n }\n\n // --- DOM ---\n\n private applyLayout() {\n const { width, align, sidePadding, maxWidth } = this.options;\n const el = this.wrapperEl;\n\n const vw = window.innerWidth;\n const clampedWidth = Math.max(10, Math.min(100, width));\n const pad = Math.max(0, Math.min(sidePadding, vw / 2 - 10));\n\n let sheetW = Math.max(20, Math.min(vw * (clampedWidth / 100), vw - pad * 2));\n if (maxWidth > 0) sheetW = Math.min(sheetW, maxWidth);\n\n let left: number;\n if (align === \"left\") {\n left = pad;\n } else if (align === \"right\") {\n left = vw - sheetW - pad;\n } else {\n left = (vw - sheetW) / 2;\n }\n\n el.style.left = `${left}px`;\n el.style.right = \"auto\";\n el.style.width = `${sheetW}px`;\n el.style.margin = \"0\";\n }\n\n private buildDOM() {\n this.wrapperEl = document.createElement(\"div\");\n this.wrapperEl.className = \"drwr-sheet\";\n this.wrapperEl.setAttribute(\"tabindex\", \"-1\");\n\n if (this.options.dragHandle) {\n this.handleEl = document.createElement(\"div\");\n this.handleEl.className = \"drwr-handle\";\n this.wrapperEl.appendChild(this.handleEl);\n }\n\n this.contentEl = document.createElement(\"div\");\n this.contentEl.className = \"drwr-content\";\n\n while (this.container.firstChild) {\n this.contentEl.appendChild(this.container.firstChild);\n }\n this.wrapperEl.appendChild(this.contentEl);\n this.container.appendChild(this.wrapperEl);\n\n if (this.options.overlay) {\n this.overlayEl = document.createElement(\"div\");\n this.overlayEl.className = \"drwr-overlay\";\n this.overlayEl.addEventListener(\"click\", () => {\n if (this.options.closeOnOverlayClick) this.close();\n });\n this.container.insertBefore(this.overlayEl, this.wrapperEl);\n }\n\n applyAriaAttributes(this.wrapperEl, this.overlayEl, {\n ariaLabel: this.options.ariaLabel,\n ariaLabelledBy: this.options.ariaLabelledBy,\n modal: this.options.modal,\n });\n this.applyLayout();\n }\n\n // --- Gestures ---\n\n private bindGestures() {\n if (this.options.dragMode === \"none\") return;\n this.cleanupGestures = attachGestures(\n this.wrapperEl,\n this.handleEl,\n this.options.dragMode === \"handle\",\n {\n onStart: () => {\n this.cancelCurrentAnimation();\n this.state = \"dragging\";\n this.dragStartH = this.currentH;\n this.wrapperEl.setAttribute(\"data-dragging\", \"true\");\n },\n onMove: (_y, deltaY) => {\n // deltaY positive = finger moved down = sheet should shrink\n let newH = this.dragStartH - deltaY;\n\n const snaps = this.snapPixels();\n const maxH = snaps[snaps.length - 1];\n const minH = snaps[0];\n\n if (newH > maxH) {\n newH = maxH;\n } else if (newH < minH) {\n const overflow = minH - newH;\n newH = minH - Math.max(0, dampen(overflow));\n }\n\n this.currentH = newH;\n this.render(newH);\n },\n onEnd: (velocityPx) => {\n this.wrapperEl.setAttribute(\"data-dragging\", \"false\");\n const velocityH = -velocityPx;\n this.state = \"animating\";\n\n const snaps = this.snapPixels();\n let targetH = resolveSnapPoint(this.currentH, velocityH, snaps, 400);\n\n // closeThreshold: if the sheet is dragged below (lowestNonZeroSnap * threshold), close it\n const nonZeroSnaps = snaps.filter((s) => s > 0);\n if (nonZeroSnaps.length > 0) {\n const lowestNonZero = nonZeroSnaps[0];\n if (this.currentH < lowestNonZero * this.options.closeThreshold) {\n targetH = 0;\n }\n }\n\n this.animateToH(targetH, velocityH);\n },\n },\n );\n }\n\n // --- Animation ---\n\n private cancelCurrentAnimation() {\n if (this.cancelAnimation) {\n this.cancelAnimation();\n this.cancelAnimation = null;\n }\n }\n\n private animateToH(targetH: number, initialVelocity = 0) {\n const wasOpen = this.state === \"open\" || this.state === \"dragging\";\n this.cancelCurrentAnimation();\n this.state = \"animating\";\n const onDone = () => {\n this.currentH = targetH;\n this.render(targetH);\n this.cancelAnimation = null;\n\n const snap = this.pixelsToSnap(targetH);\n this.currentSnap = snap;\n\n if (targetH <= 0.5) {\n this.onClosed();\n } else {\n this.state = \"open\";\n if (!wasOpen) this.options.onOpen();\n this.options.onSnap(snap);\n }\n };\n\n if (this.prefersReducedMotion()) {\n onDone();\n return;\n }\n\n const springConfig: Partial<SpringConfig> = {\n damping: this.options.damping,\n stiffness: this.options.stiffness,\n mass: this.options.mass,\n };\n\n this.cancelAnimation = animateSpring(\n this.currentH,\n targetH,\n initialVelocity,\n springConfig,\n (value) => {\n this.currentH = value;\n this.render(value);\n },\n onDone,\n );\n }\n\n // --- Rendering ---\n\n private render(h: number) {\n const viewportH = window.innerHeight;\n const visibleH = Math.max(0, h);\n const translateY = viewportH - visibleH;\n\n this.wrapperEl.style.visibility = visibleH > 0.5 ? \"visible\" : \"hidden\";\n\n this.wrapperEl.style.transform = `translate3d(0, ${translateY}px, 0)`;\n this.wrapperEl.style.height = visibleH > 0.5 ? `${visibleH}px` : \"\";\n\n if (this.overlayEl) {\n const snaps = this.snapPixels();\n const maxH = snaps[snaps.length - 1];\n const opacity = Math.max(0, Math.min(1, visibleH / (maxH * 0.3)));\n this.overlayEl.style.opacity = String(opacity);\n this.overlayEl.setAttribute(\"data-visible\", visibleH > 1 ? \"true\" : \"false\");\n }\n }\n\n // --- State changes ---\n\n /** Shared cleanup: visually close and tear down locks/traps. Does NOT fire callbacks. */\n private resetToClosedState() {\n this.cancelCurrentAnimation();\n this.currentH = 0;\n this.currentSnap = 0;\n this.render(0);\n this.state = \"closed\";\n\n if (this.overlayEl) {\n this.overlayEl.style.opacity = \"0\";\n this.overlayEl.setAttribute(\"data-visible\", \"false\");\n }\n if (this.unlockScroll) {\n this.unlockScroll();\n this.unlockScroll = null;\n }\n if (this.cleanupFocusTrap) {\n this.cleanupFocusTrap();\n this.cleanupFocusTrap = null;\n }\n document.removeEventListener(\"keydown\", this.onKeyDown);\n }\n\n private onClosed() {\n this.resetToClosedState();\n this.options.onClose();\n }\n\n // --- Keyboard ---\n\n private onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\" && this.state !== \"closed\" && this.options.closeOnEscape) {\n this.close();\n }\n };\n\n // --- Public API ---\n\n open(point?: number) {\n if (this.destroyed || this.state === \"open\") return;\n\n const sorted = [...this.options.snapPoints].sort((a, b) => a - b);\n let target: number;\n if (point !== undefined) {\n if (!sorted.includes(point) || point === 0) {\n console.warn(`[drwr] snap point ${point} not valid for open()`);\n return;\n }\n target = point;\n } else {\n target = sorted[sorted.length - 1];\n }\n\n document.addEventListener(\"keydown\", this.onKeyDown);\n this.animateToH(this.snapToPixels(target));\n if (!this.unlockScroll) this.unlockScroll = lockScroll();\n if (this.options.trapFocus && !this.cleanupFocusTrap) {\n this.cleanupFocusTrap = createFocusTrap(this.wrapperEl);\n }\n }\n\n close() {\n if (this.destroyed || this.state === \"closed\") return;\n document.removeEventListener(\"keydown\", this.onKeyDown);\n this.animateToH(0);\n }\n\n snapTo(point: number) {\n if (this.destroyed) return;\n if (!this.options.snapPoints.includes(point)) {\n console.warn(`[drwr] snap point ${point} not in snapPoints`);\n return;\n }\n if (point === 0) {\n this.close();\n } else {\n if (this.state === \"closed\") {\n document.addEventListener(\"keydown\", this.onKeyDown);\n if (!this.unlockScroll) this.unlockScroll = lockScroll();\n if (this.options.trapFocus && !this.cleanupFocusTrap) {\n this.cleanupFocusTrap = createFocusTrap(this.wrapperEl);\n }\n }\n this.animateToH(this.snapToPixels(point));\n }\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n\n this.cancelCurrentAnimation();\n document.removeEventListener(\"keydown\", this.onKeyDown);\n\n if (this.cleanupGestures) {\n this.cleanupGestures();\n this.cleanupGestures = null;\n }\n if (this.cleanupFocusTrap) {\n this.cleanupFocusTrap();\n this.cleanupFocusTrap = null;\n }\n if (this.unlockScroll) {\n this.unlockScroll();\n this.unlockScroll = null;\n }\n if (this.onPageShow) {\n window.removeEventListener(\"pageshow\", this.onPageShow);\n this.onPageShow = null;\n }\n if (this.onResize) {\n window.removeEventListener(\"resize\", this.onResize);\n this.onResize = null;\n }\n\n removeAriaAttributes(this.wrapperEl, this.overlayEl);\n\n while (this.contentEl.firstChild) {\n this.container.appendChild(this.contentEl.firstChild);\n }\n\n this.wrapperEl.remove();\n this.overlayEl?.remove();\n\n }\n\n setSnapPoints(points: number[]) {\n if (this.destroyed) return;\n this.options.snapPoints = Sheet.sanitizeSnapPoints(points);\n\n if (this.state !== \"closed\" && this.currentSnap > 0) {\n if (!this.options.snapPoints.includes(this.currentSnap)) {\n const nearest = this.pixelsToSnap(this.currentH);\n this.animateToH(this.snapToPixels(nearest));\n }\n }\n }\n\n /** Re-measure content height and recompute current position. Call after dynamic content changes. */\n refresh() {\n if (this.destroyed) return;\n this.naturalContentH = this.getContentHeight();\n if (this.state !== \"closed\" && this.currentSnap > 0) {\n this.currentH = this.snapToPixels(this.currentSnap);\n this.render(this.currentH);\n }\n }\n\n setOptions(opts: MutableSheetOptions) {\n if (this.destroyed) return;\n if (opts.snapPoints !== undefined) this.options.snapPoints = Sheet.sanitizeSnapPoints(opts.snapPoints);\n if (opts.damping !== undefined) this.options.damping = opts.damping;\n if (opts.stiffness !== undefined) this.options.stiffness = opts.stiffness;\n if (opts.mass !== undefined) this.options.mass = opts.mass;\n if (opts.closeThreshold !== undefined) this.options.closeThreshold = opts.closeThreshold;\n if (opts.topPadding !== undefined) this.options.topPadding = opts.topPadding;\n if (opts.closeOnEscape !== undefined) this.options.closeOnEscape = opts.closeOnEscape;\n if (opts.closeOnOverlayClick !== undefined) this.options.closeOnOverlayClick = opts.closeOnOverlayClick;\n if (opts.ariaLabel !== undefined) this.options.ariaLabel = opts.ariaLabel;\n if (opts.ariaLabelledBy !== undefined) this.options.ariaLabelledBy = opts.ariaLabelledBy;\n if (opts.modal !== undefined) this.options.modal = opts.modal;\n if (opts.maxWidth !== undefined) this.options.maxWidth = opts.maxWidth;\n if (opts.reducedMotion !== undefined) this.options.reducedMotion = opts.reducedMotion;\n if (opts.onOpen !== undefined) this.options.onOpen = opts.onOpen;\n if (opts.onClose !== undefined) this.options.onClose = opts.onClose;\n if (opts.onSnap !== undefined) this.options.onSnap = opts.onSnap;\n\n if (opts.ariaLabel !== undefined || opts.ariaLabelledBy !== undefined || opts.modal !== undefined) {\n applyAriaAttributes(this.wrapperEl, this.overlayEl, {\n ariaLabel: this.options.ariaLabel,\n ariaLabelledBy: this.options.ariaLabelledBy,\n modal: this.options.modal,\n });\n }\n if (opts.width !== undefined || opts.align !== undefined || opts.sidePadding !== undefined || opts.maxWidth !== undefined) {\n if (opts.width !== undefined) this.options.width = opts.width;\n if (opts.align !== undefined) this.options.align = opts.align;\n if (opts.sidePadding !== undefined) this.options.sidePadding = opts.sidePadding;\n this.applyLayout();\n }\n\n // Recompute position when size-affecting options changed\n const sizeChanged = opts.topPadding !== undefined || opts.snapPoints !== undefined;\n if (sizeChanged && this.state !== \"closed\" && this.currentSnap > 0) {\n if (opts.snapPoints !== undefined && !this.options.snapPoints.includes(this.currentSnap)) {\n const nearest = this.pixelsToSnap(this.currentH);\n this.animateToH(this.snapToPixels(nearest));\n } else {\n this.currentH = this.snapToPixels(this.currentSnap);\n this.render(this.currentH);\n }\n }\n }\n\n setLayout(opts: { width?: number; align?: SheetAlign; sidePadding?: number }) {\n if (this.destroyed) return;\n if (opts.width !== undefined) this.options.width = opts.width;\n if (opts.align !== undefined) this.options.align = opts.align;\n if (opts.sidePadding !== undefined) this.options.sidePadding = opts.sidePadding;\n this.applyLayout();\n }\n\n get isOpen(): boolean {\n return this.state !== \"closed\";\n }\n\n get currentSnapPoint(): number {\n return this.currentSnap;\n }\n}\n"],"mappings":"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","names":["DEFAULT_SPRING","springStep","current","target","velocity","dt","config","stiffness","damping","mass","displacement","springForce","dampingForce","acceleration","newVelocity","isAtRest","threshold","animateSpring","from","to","initialVelocity","onUpdate","onComplete","cfg","position","lastTime","frameId","tick","time","state","VelocityTracker","maxSamples","value","recent","oldest","i","resolveSnapPoint","snapPoints","flickThreshold","hardFlickThreshold","sorted","a","b","sp","closest","closestDist","dist","DIRECTION_LOCK_THRESHOLD","SCROLL_LOCK_THRESHOLD","findScrollableAncestor","target","sheetEl","el","overflowY","attachGestures","handleEl","handleOnly","callbacks","tracker","VelocityTracker","state","onPointerDown","e","isHandle","scrollable","onPointerMove","dx","dy","deltaY","st","atTop","atBottom","draggingDown","onPointerUp","velocity","onPointerCancel","onContextMenu","FOCUSABLE_SELECTORS","createFocusTrap","container","previousFocus","getFocusable","onKeyDown","e","focusable","first","last","activate","deactivate","applyAriaAttributes","sheetEl","overlayEl","opts","removeAriaAttributes","lockScroll","body","html","originalBodyOverflow","originalHtmlOverflow","preventTouch","el","dampen","v","Sheet","_Sheet","container","options","snaps","defaultSnap","lockScroll","createFocusTrap","e","raw","valid","n","deduped","a","b","snap","maxH","s","px","closest","closestDist","i","dist","width","align","sidePadding","maxWidth","el","vw","clampedWidth","pad","sheetW","left","applyAriaAttributes","attachGestures","_y","deltaY","newH","minH","overflow","velocityPx","velocityH","targetH","resolveSnapPoint","nonZeroSnaps","lowestNonZero","initialVelocity","wasOpen","onDone","springConfig","animateSpring","value","h","viewportH","visibleH","translateY","opacity","point","sorted","target","removeAriaAttributes","points","nearest","opts"]}
package/dist/style.css ADDED
@@ -0,0 +1,89 @@
1
+ :root {
2
+ --drwr-bg: #fff;
3
+ --drwr-radius: 16px;
4
+ --drwr-handle-width: 32px;
5
+ --drwr-handle-height: 4px;
6
+ --drwr-handle-color: rgba(0, 0, 0, 0.25);
7
+ --drwr-overlay-color: rgba(0, 0, 0, 0.4);
8
+ --drwr-shadow:
9
+ 0 -1px 0 rgba(0, 0, 0, 0.04),
10
+ 0 -8px 32px rgba(0, 0, 0, 0.12),
11
+ 0 -24px 60px rgba(0, 0, 0, 0.06);
12
+ --drwr-z-index: 9999;
13
+ --drwr-border: 1px solid rgba(0, 0, 0, 0.06);
14
+ }
15
+
16
+ .drwr-overlay {
17
+ position: fixed;
18
+ inset: 0;
19
+ z-index: var(--drwr-z-index);
20
+ background: var(--drwr-overlay-color);
21
+ opacity: 0;
22
+ pointer-events: none;
23
+ -webkit-tap-highlight-color: transparent;
24
+ }
25
+
26
+ .drwr-overlay[data-visible="true"] {
27
+ pointer-events: auto;
28
+ }
29
+
30
+ .drwr-sheet {
31
+ position: fixed;
32
+ top: 0;
33
+ left: 0;
34
+ right: 0;
35
+ z-index: calc(var(--drwr-z-index) + 1);
36
+ display: flex;
37
+ flex-direction: column;
38
+ background: var(--drwr-bg);
39
+ border-radius: var(--drwr-radius) var(--drwr-radius) 0 0;
40
+ border-top: var(--drwr-border);
41
+ border-left: var(--drwr-border);
42
+ border-right: var(--drwr-border);
43
+ box-shadow: var(--drwr-shadow);
44
+ transform: translate3d(0, 100vh, 0);
45
+ touch-action: none;
46
+ will-change: transform;
47
+ outline: none;
48
+ -webkit-tap-highlight-color: transparent;
49
+ overscroll-behavior: contain;
50
+ visibility: hidden;
51
+ }
52
+
53
+
54
+ .drwr-sheet[data-dragging="true"] {
55
+ user-select: none;
56
+ cursor: grabbing;
57
+ }
58
+
59
+ .drwr-handle {
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ flex-shrink: 0;
64
+ padding: 14px 0 10px;
65
+ cursor: grab;
66
+ -webkit-tap-highlight-color: transparent;
67
+ }
68
+
69
+ .drwr-handle::before {
70
+ content: "";
71
+ display: block;
72
+ width: var(--drwr-handle-width);
73
+ height: var(--drwr-handle-height);
74
+ border-radius: 99px;
75
+ background: var(--drwr-handle-color);
76
+ }
77
+
78
+ .drwr-handle:active {
79
+ cursor: grabbing;
80
+ }
81
+
82
+ .drwr-content {
83
+ flex: 1;
84
+ overflow-y: auto;
85
+ overscroll-behavior: contain;
86
+ -webkit-overflow-scrolling: touch;
87
+ touch-action: pan-y;
88
+ padding: 0 16px 16px;
89
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@alexapvl/drwr",
3
+ "version": "0.1.0",
4
+ "description": "A zero-dependency, framework-agnostic bottom sheet / drawer component",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "./style.css": "./dist/style.css"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "sideEffects": [
26
+ "**/*.css"
27
+ ],
28
+ "keywords": [
29
+ "drawer",
30
+ "sheet",
31
+ "bottom-sheet",
32
+ "modal",
33
+ "mobile",
34
+ "gesture",
35
+ "drag",
36
+ "spring",
37
+ "a11y"
38
+ ],
39
+ "author": "",
40
+ "license": "MIT",
41
+ "devDependencies": {
42
+ "tsup": "^8.0.0",
43
+ "typescript": "^5.4.0",
44
+ "vite": "^6.0.0"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "dev": "vite demo",
49
+ "typecheck": "tsc --noEmit"
50
+ }
51
+ }