@dodlhuat/basix 1.2.8 → 1.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dodlhuat/basix",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
4
4
  "description": "Basix is intended as a starter for the rapid development of a design. Each design element can be added individually to include only the data required. It is using plain javascript / typescript and therefore is not dependent on any plugin.",
5
5
  "exports": {
6
6
  "./css/*": "./css/*",
@@ -1,224 +0,0 @@
1
- import { sanitizeHtml } from './utils.js';
2
-
3
- interface BottomSheetOptions {
4
- content: string;
5
- header?: string;
6
- footer?: string;
7
- closeable?: boolean;
8
- snapHeight?: 'auto' | 'half' | 'full';
9
- onClose?: () => void;
10
- }
11
-
12
- class BottomSheet {
13
- private readonly content: string;
14
- private readonly header?: string;
15
- private readonly footer?: string;
16
- private readonly closeable: boolean;
17
- private snapHeight: 'auto' | 'half' | 'full';
18
- private readonly onClose?: () => void;
19
-
20
- private wrapper: HTMLElement | null = null;
21
- private sheet: HTMLElement | null = null;
22
- private handle: HTMLElement | null = null;
23
- private body: HTMLElement | null = null;
24
-
25
- // Touch drag state
26
- private dragStartY = 0;
27
- private currentDragY = 0;
28
- private isDragging = false;
29
-
30
- constructor(options: BottomSheetOptions) {
31
- this.content = options.content;
32
- this.header = options.header;
33
- this.footer = options.footer;
34
- this.closeable = options.closeable ?? true;
35
- this.snapHeight = options.snapHeight ?? 'auto';
36
- this.onClose = options.onClose;
37
-
38
- this.hide = this.hide.bind(this);
39
- this.handleEscape = this.handleEscape.bind(this);
40
- this.handleBackdropClick = this.handleBackdropClick.bind(this);
41
- this.handleTouchStart = this.handleTouchStart.bind(this);
42
- this.handleTouchMove = this.handleTouchMove.bind(this);
43
- this.handleTouchEnd = this.handleTouchEnd.bind(this);
44
- }
45
-
46
- public show(): void {
47
- this.hide();
48
-
49
- const wrapper = document.createElement('div');
50
- wrapper.className = 'bottom-sheet-wrapper';
51
- wrapper.innerHTML = this.buildTemplate();
52
- document.body.append(wrapper);
53
-
54
- this.wrapper = wrapper;
55
- this.sheet = wrapper.querySelector('.bottom-sheet');
56
- this.handle = wrapper.querySelector('.bottom-sheet-handle');
57
- this.body = wrapper.querySelector('.bottom-sheet-body');
58
-
59
- if (this.closeable) {
60
- const backdrop = wrapper.querySelector('.bottom-sheet-backdrop');
61
- backdrop?.addEventListener('click', this.handleBackdropClick);
62
- document.addEventListener('keydown', this.handleEscape);
63
-
64
- const closeBtn = wrapper.querySelector('.close');
65
- closeBtn?.addEventListener('click', this.hide);
66
- }
67
-
68
- if (this.handle) {
69
- this.handle.addEventListener('touchstart', this.handleTouchStart, { passive: true });
70
- this.handle.addEventListener('touchmove', this.handleTouchMove, { passive: false });
71
- this.handle.addEventListener('touchend', this.handleTouchEnd);
72
- }
73
-
74
- if (this.body) {
75
- this.updateScrollMask();
76
- this.body.addEventListener('scroll', () => this.updateScrollMask());
77
- }
78
-
79
- document.body.style.overflow = 'hidden';
80
-
81
- requestAnimationFrame(() => {
82
- wrapper.classList.add('is-visible');
83
- });
84
- }
85
-
86
- public hide(): void {
87
- if (!this.wrapper) return;
88
-
89
- const backdrop = this.wrapper.querySelector('.bottom-sheet-backdrop');
90
- backdrop?.removeEventListener('click', this.handleBackdropClick);
91
- document.removeEventListener('keydown', this.handleEscape);
92
-
93
- if (this.handle) {
94
- this.handle.removeEventListener('touchstart', this.handleTouchStart);
95
- this.handle.removeEventListener('touchmove', this.handleTouchMove);
96
- this.handle.removeEventListener('touchend', this.handleTouchEnd);
97
- }
98
-
99
- document.body.style.overflow = '';
100
- this.wrapper.classList.remove('is-visible');
101
-
102
- const wrapper = this.wrapper;
103
- this.wrapper = null;
104
- this.sheet = null;
105
- this.handle = null;
106
- this.body = null;
107
-
108
- setTimeout(() => {
109
- wrapper.remove();
110
- this.onClose?.();
111
- }, 420);
112
- }
113
-
114
- public snapTo(height: 'auto' | 'half' | 'full'): void {
115
- if (!this.sheet) return;
116
-
117
- this.sheet.classList.remove(`snap-${this.snapHeight}`);
118
- this.snapHeight = height;
119
-
120
- if (height !== 'auto') {
121
- this.sheet.classList.add(`snap-${height}`);
122
- }
123
- }
124
-
125
- private handleEscape(e: KeyboardEvent): void {
126
- if (e.key === 'Escape') this.hide();
127
- }
128
-
129
- private handleBackdropClick(e: Event): void {
130
- if ((e.target as HTMLElement)?.classList.contains('bottom-sheet-backdrop')) {
131
- this.hide();
132
- }
133
- }
134
-
135
- private handleTouchStart(e: TouchEvent): void {
136
- this.dragStartY = e.touches[0].clientY;
137
- this.currentDragY = 0;
138
- this.isDragging = true;
139
-
140
- if (this.sheet) {
141
- this.sheet.style.transition = 'none';
142
- }
143
- }
144
-
145
- private handleTouchMove(e: TouchEvent): void {
146
- if (!this.isDragging || !this.sheet) return;
147
-
148
- const deltaY = e.touches[0].clientY - this.dragStartY;
149
-
150
- // Rubber-band resistance going upward
151
- if (deltaY < 0) {
152
- const resistance = Math.log(1 + Math.abs(deltaY)) * 4;
153
- this.currentDragY = -resistance;
154
- } else {
155
- this.currentDragY = deltaY;
156
- }
157
-
158
- const isDesktop = window.innerWidth >= 768;
159
- const translateX = isDesktop ? '-50%' : '0';
160
- this.sheet.style.transform = `translateX(${translateX}) translateY(${this.currentDragY}px)`;
161
- e.preventDefault();
162
- }
163
-
164
- private handleTouchEnd(): void {
165
- if (!this.isDragging || !this.sheet) return;
166
- this.isDragging = false;
167
-
168
- const threshold = this.sheet.offsetHeight * 0.3;
169
-
170
- if (this.currentDragY > threshold) {
171
- this.hide();
172
- } else {
173
- // Spring back
174
- this.sheet.style.transition = '';
175
- this.sheet.style.transform = '';
176
- }
177
- }
178
-
179
- private updateScrollMask(): void {
180
- if (!this.body) return;
181
- const canScroll = this.body.scrollHeight > this.body.clientHeight;
182
- const atBottom = this.body.scrollTop + this.body.clientHeight >= this.body.scrollHeight - 4;
183
- this.body.classList.toggle('is-scrollable', canScroll && !atBottom);
184
- }
185
-
186
- private buildTemplate(): string {
187
- const snapClass = this.snapHeight !== 'auto' ? ` snap-${this.snapHeight}` : '';
188
-
189
- const closeButton = this.closeable
190
- ? `<div class="icon icon-close close"></div>`
191
- : '';
192
-
193
- const headerHtml = this.header !== undefined
194
- ? `<div class="bottom-sheet-header has-divider">
195
- <span class="title">${sanitizeHtml(this.header)}</span>
196
- ${closeButton}
197
- </div>`
198
- : '';
199
-
200
- const footerHtml = this.footer !== undefined
201
- ? `<div class="bottom-sheet-footer">${sanitizeHtml(this.footer)}</div>`
202
- : '';
203
-
204
- return `
205
- <div class="bottom-sheet${snapClass}">
206
- <div class="bottom-sheet-handle" role="button" aria-label="Drag to dismiss"></div>
207
- ${headerHtml}
208
- <div class="bottom-sheet-body">${sanitizeHtml(this.content)}</div>
209
- ${footerHtml}
210
- </div>
211
- <div class="bottom-sheet-backdrop"></div>
212
- `;
213
- }
214
-
215
- public isVisible(): boolean {
216
- return this.wrapper !== null && document.body.contains(this.wrapper);
217
- }
218
-
219
- public destroy(): void {
220
- this.hide();
221
- }
222
- }
223
-
224
- export { BottomSheet, type BottomSheetOptions };