@surdeddd/bottom-sheet 0.23.1 → 0.23.2

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/dist/webgl.js CHANGED
@@ -108,6 +108,10 @@ uniform float u_sheen;
108
108
  uniform float u_glass;
109
109
  uniform float u_hasContent;
110
110
  uniform sampler2D u_content;
111
+ uniform float u_hasScroll;
112
+ uniform sampler2D u_scroll;
113
+ uniform vec4 u_scrollRect;
114
+ uniform vec2 u_scrollMap;
111
115
 
112
116
  float roundedBoxSDF(vec2 p, vec2 halfSize, float r) {
113
117
  vec2 q = abs(p) - halfSize + r;
@@ -143,6 +147,19 @@ void main() {
143
147
  content = texture2D(u_content, glassUv);
144
148
  }
145
149
 
150
+ vec4 scroll = vec4(0.0);
151
+ if (u_hasScroll > 0.5 &&
152
+ glassUv.x >= u_scrollRect.x && glassUv.x <= u_scrollRect.z &&
153
+ glassUv.y >= u_scrollRect.y && glassUv.y <= u_scrollRect.w) {
154
+ vec2 st = vec2(
155
+ (glassUv.x - u_scrollRect.x) / max(u_scrollRect.z - u_scrollRect.x, 0.000001),
156
+ (glassUv.y - u_scrollRect.y) * u_scrollMap.x + u_scrollMap.y
157
+ );
158
+ if (st.y >= 0.0 && st.y <= 1.0) {
159
+ scroll = texture2D(u_scroll, st);
160
+ }
161
+ }
162
+
146
163
  float edge = 1.0 - smoothstep(0.0, 2.5, abs(d));
147
164
  float topLight = pow(1.0 - down, 3.0);
148
165
  float sheen = (edge * 0.55 + topLight * 0.25) * u_sheen;
@@ -150,8 +167,10 @@ void main() {
150
167
  vec3 panelRgb = (u_color.rgb + vec3(sheen)) * u_color.a;
151
168
  float panelA = u_color.a;
152
169
 
153
- vec3 rgb = content.rgb + panelRgb * (1.0 - content.a);
154
- float alpha = content.a + panelA * (1.0 - content.a);
170
+ vec3 baseRgb = content.rgb + panelRgb * (1.0 - content.a);
171
+ float baseA = content.a + panelA * (1.0 - content.a);
172
+ vec3 rgb = scroll.rgb + baseRgb * (1.0 - scroll.a);
173
+ float alpha = scroll.a + baseA * (1.0 - scroll.a);
155
174
 
156
175
  rgb *= surface;
157
176
  alpha *= surface;
@@ -211,6 +230,10 @@ var createSurfaceProgram = (gl, highp = true) => {
211
230
  const uGlass = gl.getUniformLocation(program, "u_glass");
212
231
  const uHasContent = gl.getUniformLocation(program, "u_hasContent");
213
232
  const uContent = gl.getUniformLocation(program, "u_content");
233
+ const uHasScroll = gl.getUniformLocation(program, "u_hasScroll");
234
+ const uScroll = gl.getUniformLocation(program, "u_scroll");
235
+ const uScrollRect = gl.getUniformLocation(program, "u_scrollRect");
236
+ const uScrollMap = gl.getUniformLocation(program, "u_scrollMap");
214
237
  return {
215
238
  draw: (frame, state) => {
216
239
  const { drawingBufferWidth: w, drawingBufferHeight: h } = gl;
@@ -237,14 +260,25 @@ var createSurfaceProgram = (gl, highp = true) => {
237
260
  gl.uniform1f(uBend, state.bend * dpr);
238
261
  gl.uniform1f(uSheen, state.sheen);
239
262
  gl.uniform1f(uGlass, state.glass);
263
+ gl.uniform1i(uContent, 0);
264
+ gl.uniform1i(uScroll, 1);
240
265
  if (state.content) {
241
266
  gl.activeTexture(gl.TEXTURE0);
242
267
  gl.bindTexture(gl.TEXTURE_2D, state.content);
243
- gl.uniform1i(uContent, 0);
244
268
  gl.uniform1f(uHasContent, 1);
245
269
  } else {
246
270
  gl.uniform1f(uHasContent, 0);
247
271
  }
272
+ if (state.scroll) {
273
+ gl.activeTexture(gl.TEXTURE1);
274
+ gl.bindTexture(gl.TEXTURE_2D, state.scroll.texture);
275
+ gl.uniform4f(uScrollRect, ...state.scroll.rect);
276
+ gl.uniform2f(uScrollMap, ...state.scroll.map);
277
+ gl.uniform1f(uHasScroll, 1);
278
+ gl.activeTexture(gl.TEXTURE0);
279
+ } else {
280
+ gl.uniform1f(uHasScroll, 0);
281
+ }
248
282
  gl.drawArrays(gl.TRIANGLES, 0, 3);
249
283
  },
250
284
  destroy: () => {
@@ -313,13 +347,13 @@ var roundedPath = (ctx, x, y, w, h, r) => {
313
347
  ctx.arcTo(x, y, x + w, y, rad);
314
348
  ctx.closePath();
315
349
  };
316
- var drawBox = (ctx, cs, rect, rootRect) => {
350
+ var drawBox = (ctx, cs, rect, origin) => {
317
351
  const bg = cs.backgroundColor;
318
352
  const borderWidth = parseFloat(cs.borderTopWidth) || 0;
319
353
  const hasBorder = borderWidth > 0 && isPaintedColor(cs.borderTopColor);
320
354
  if (!isPaintedColor(bg) && !hasBorder) return false;
321
- const x = rect.left - rootRect.left;
322
- const y = rect.top - rootRect.top;
355
+ const x = rect.left - origin.x;
356
+ const y = rect.top - origin.y;
323
357
  const radius = parseFloat(cs.borderTopLeftRadius) || 0;
324
358
  if (isPaintedColor(bg)) {
325
359
  roundedPath(ctx, x, y, rect.width, rect.height, radius);
@@ -334,15 +368,13 @@ var drawBox = (ctx, cs, rect, rootRect) => {
334
368
  }
335
369
  return true;
336
370
  };
337
- var drawImage = (ctx, img, rootRect) => {
371
+ var drawImage = (ctx, img, rect, origin) => {
338
372
  if (!img.complete || img.naturalWidth === 0) return false;
339
- const rect = img.getBoundingClientRect();
340
- if (rect.width < 1 || rect.height < 1) return false;
341
373
  try {
342
374
  ctx.drawImage(
343
375
  img,
344
- rect.left - rootRect.left,
345
- rect.top - rootRect.top,
376
+ rect.left - origin.x,
377
+ rect.top - origin.y,
346
378
  rect.width,
347
379
  rect.height
348
380
  );
@@ -351,37 +383,41 @@ var drawImage = (ctx, img, rootRect) => {
351
383
  return false;
352
384
  }
353
385
  };
354
- var captureContent = (gl, root, dpr) => {
355
- const rootRect = root.getBoundingClientRect();
356
- const w = Math.max(1, Math.round(rootRect.width * dpr));
357
- const h = Math.max(1, Math.round(rootRect.height * dpr));
386
+ var createLayer = (width, height, scale) => {
387
+ const w = Math.max(1, Math.round(width * scale));
388
+ const h = Math.max(1, Math.round(height * scale));
358
389
  if (w < 2 || h < 2) return null;
359
- const surface = document.createElement("canvas");
360
- surface.width = w;
361
- surface.height = h;
362
- const ctx = surface.getContext("2d");
390
+ const canvas = document.createElement("canvas");
391
+ canvas.width = w;
392
+ canvas.height = h;
393
+ const ctx = canvas.getContext("2d");
363
394
  if (!ctx) return null;
364
- ctx.scale(dpr, dpr);
365
- const hiddenNodes = [];
366
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
395
+ ctx.scale(scale, scale);
396
+ return { canvas, ctx };
397
+ };
398
+ var paintTree = (ctx, walker, origin, hidden) => {
367
399
  let painted = 0;
368
400
  while (walker.nextNode()) {
369
401
  const el = walker.currentNode;
370
- if (el.tagName === "IMG") {
371
- if (drawImage(ctx, el, rootRect)) {
402
+ const isImage = el.tagName === "IMG";
403
+ if (!isImage && !isRenderable(el)) continue;
404
+ const rect = el.getBoundingClientRect();
405
+ if (rect.width < 1 || rect.height < 1) continue;
406
+ if (rect.bottom <= origin.y || rect.top >= origin.y + origin.height) {
407
+ continue;
408
+ }
409
+ if (isImage) {
410
+ if (drawImage(ctx, el, rect, origin)) {
372
411
  painted++;
373
- hiddenNodes.push(el);
412
+ hidden.push(el);
374
413
  }
375
414
  continue;
376
415
  }
377
- if (!isRenderable(el)) continue;
378
- const rect = el.getBoundingClientRect();
379
- if (rect.width < 1 || rect.height < 1) continue;
380
416
  const cs = getComputedStyle(el);
381
- const boxed = drawBox(ctx, cs, rect, rootRect);
417
+ const boxed = drawBox(ctx, cs, rect, origin);
382
418
  if (boxed) {
383
419
  painted++;
384
- hiddenNodes.push(el);
420
+ hidden.push(el);
385
421
  }
386
422
  const text = directText(el);
387
423
  if (!text) continue;
@@ -395,12 +431,12 @@ var captureContent = (gl, root, dpr) => {
395
431
  rect.width - padLeft - (parseFloat(cs.paddingRight) || 0)
396
432
  );
397
433
  const lineHeight = parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.4;
398
- const x = rect.left - rootRect.left + padLeft;
399
- const y = rect.top - rootRect.top + padTop;
434
+ const x = rect.left - origin.x + padLeft;
435
+ const y = rect.top - origin.y + padTop;
400
436
  const lines = wrapLines(ctx, text, innerWidth);
401
437
  lines.forEach((line, i) => {
402
438
  const lineY = y + i * lineHeight;
403
- if (lineY > rootRect.height) return;
439
+ if (lineY > origin.height) return;
404
440
  let lineX = x;
405
441
  if (cs.textAlign === "center") {
406
442
  lineX = x + (innerWidth - ctx.measureText(line).width) / 2;
@@ -411,10 +447,12 @@ var captureContent = (gl, root, dpr) => {
411
447
  });
412
448
  if (lines.length) {
413
449
  painted++;
414
- if (!boxed) hiddenNodes.push(el);
450
+ if (!boxed) hidden.push(el);
415
451
  }
416
452
  }
417
- if (!painted) return null;
453
+ return painted;
454
+ };
455
+ var upload = (gl, canvas) => {
418
456
  const texture = gl.createTexture();
419
457
  if (!texture) return null;
420
458
  gl.bindTexture(gl.TEXTURE_2D, texture);
@@ -426,7 +464,7 @@ var captureContent = (gl, root, dpr) => {
426
464
  gl.RGBA,
427
465
  gl.RGBA,
428
466
  gl.UNSIGNED_BYTE,
429
- surface
467
+ canvas
430
468
  );
431
469
  } catch {
432
470
  gl.deleteTexture(texture);
@@ -436,7 +474,78 @@ var captureContent = (gl, root, dpr) => {
436
474
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
437
475
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
438
476
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
439
- return { texture, width: w, height: h, hiddenNodes };
477
+ return texture;
478
+ };
479
+ var scaleFor = (gl, width, height, dpr) => {
480
+ const max = Number(gl.getParameter(gl.MAX_TEXTURE_SIZE)) || 4096;
481
+ return Math.min(dpr, max / Math.max(width, height, 1));
482
+ };
483
+ var captureScroll = (gl, scroller, dpr, requestedAbove, skip, hidden) => {
484
+ const box = scroller.getBoundingClientRect();
485
+ const width = scroller.clientWidth;
486
+ const view = scroller.clientHeight;
487
+ if (width < 1 || view < 1) return null;
488
+ const top = scroller.scrollTop;
489
+ const above = Math.max(0, Math.min(requestedAbove, top));
490
+ const height = view + above;
491
+ const layer = createLayer(width, height, scaleFor(gl, width, height, dpr));
492
+ if (!layer) return null;
493
+ const origin = {
494
+ x: box.left + scroller.clientLeft,
495
+ y: box.top + scroller.clientTop - above,
496
+ height
497
+ };
498
+ const walker = document.createTreeWalker(scroller, NodeFilter.SHOW_ELEMENT, {
499
+ acceptNode: (node) => skip?.(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT
500
+ });
501
+ const drawn = [];
502
+ if (!paintTree(layer.ctx, walker, origin, drawn)) return null;
503
+ const texture = upload(gl, layer.canvas);
504
+ if (!texture) return null;
505
+ hidden.push(...drawn);
506
+ return { texture, top, above, height };
507
+ };
508
+ var captureContent = (gl, root, dpr, options = {}) => {
509
+ const rootRect = root.getBoundingClientRect();
510
+ const scroller = options.scroller && options.scroller !== root && root.contains(options.scroller) ? options.scroller : null;
511
+ const skip = options.skip;
512
+ const hiddenNodes = [];
513
+ let texture = null;
514
+ const layer = createLayer(
515
+ rootRect.width,
516
+ rootRect.height,
517
+ scaleFor(gl, rootRect.width, rootRect.height, dpr)
518
+ );
519
+ if (layer) {
520
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
521
+ acceptNode: (node) => {
522
+ const el = node;
523
+ if (scroller && el.parentElement === scroller) {
524
+ return NodeFilter.FILTER_REJECT;
525
+ }
526
+ return skip?.(el) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
527
+ }
528
+ });
529
+ const drawn = [];
530
+ const origin = {
531
+ x: rootRect.left,
532
+ y: rootRect.top,
533
+ height: rootRect.height
534
+ };
535
+ if (paintTree(layer.ctx, walker, origin, drawn)) {
536
+ texture = upload(gl, layer.canvas);
537
+ if (texture) hiddenNodes.push(...drawn);
538
+ }
539
+ }
540
+ const scroll = scroller ? captureScroll(gl, scroller, dpr, options.above ?? 0, skip, hiddenNodes) : null;
541
+ if (!texture && !scroll) return null;
542
+ return {
543
+ texture,
544
+ width: layer?.canvas.width ?? 0,
545
+ height: layer?.canvas.height ?? 0,
546
+ hiddenNodes,
547
+ scroll
548
+ };
440
549
  };
441
550
  var hideCapturedText = (nodes) => {
442
551
  const restore = nodes.map((el) => ({
@@ -593,16 +702,46 @@ var webglRenderer = (options = {}) => ({
593
702
  const releaseCapture = () => {
594
703
  releaseText?.();
595
704
  releaseText = null;
596
- if (capture && handle) handle.gl.deleteTexture(capture.texture);
705
+ if (capture && handle) {
706
+ if (capture.texture) handle.gl.deleteTexture(capture.texture);
707
+ if (capture.scroll) handle.gl.deleteTexture(capture.scroll.texture);
708
+ }
597
709
  capture = null;
598
710
  };
711
+ const fitted = () => element.hasAttribute("data-bs-fit-content") && element.dataset.mode === "bottom";
599
712
  const takeCapture = () => {
600
713
  if (!liftContent || disposed || !handle || capture) return;
601
- const next = captureContent(handle.gl, element, dprOf());
714
+ const pinned = fitted();
715
+ const next = captureContent(handle.gl, element, dprOf(), {
716
+ scroller: ctx.scrollContainer ?? null,
717
+ above: pinned ? Math.max(0, ctx.getMaxAxisSize() - ctx.getSize()) : 0,
718
+ skip: pinned ? (el) => el.classList.contains("bs-footer") : void 0
719
+ });
602
720
  if (!next) return;
603
721
  capture = next;
604
722
  releaseText = hideCapturedText(next.hiddenNodes);
605
723
  };
724
+ const scrollLayer = (frame) => {
725
+ const scroll = capture?.scroll;
726
+ const scroller = ctx.scrollContainer;
727
+ if (!scroll || !scroller) return null;
728
+ const box = scroller.getBoundingClientRect();
729
+ const left = box.left + scroller.clientLeft - frame.x;
730
+ const top = box.top + scroller.clientTop - frame.y;
731
+ return {
732
+ texture: scroll.texture,
733
+ rect: [
734
+ left / frame.width,
735
+ top / frame.height,
736
+ (left + scroller.clientWidth) / frame.width,
737
+ (top + scroller.clientHeight) / frame.height
738
+ ],
739
+ map: [
740
+ frame.height / scroll.height,
741
+ (scroll.above + scroller.scrollTop - scroll.top) / scroll.height
742
+ ]
743
+ };
744
+ };
606
745
  const paint = () => {
607
746
  if (disposed || !handle || !program) return;
608
747
  const frame = readFrame();
@@ -615,18 +754,20 @@ var webglRenderer = (options = {}) => ({
615
754
  dpr,
616
755
  sheen: sheenStrength,
617
756
  glass: glassStrength * motion,
618
- content: capture?.texture ?? null
757
+ content: capture?.texture ?? null,
758
+ scroll: scrollLayer(frame)
619
759
  });
620
760
  };
621
761
  const tick = () => {
622
762
  if (disposed) return;
623
- paint();
624
763
  const held = ctx.isDragging();
764
+ if (held || !ctx.isAnimating()) paint();
625
765
  bend *= held ? BEND_DECAY_HELD : BEND_DECAY_FREE;
626
766
  const flat = Math.abs(bend) < BEND_REST;
627
767
  if (!held && flat && capture) {
628
768
  bend = 0;
629
769
  releaseCapture();
770
+ paint();
630
771
  }
631
772
  if (!held && flat && !ctx.isAnimating()) {
632
773
  bend = 0;
@@ -654,7 +795,7 @@ var webglRenderer = (options = {}) => ({
654
795
  );
655
796
  ctx.addTeardown(
656
797
  ctx.on("dragstart", () => {
657
- takeCapture();
798
+ queueMicrotask(takeCapture);
658
799
  wake();
659
800
  })
660
801
  );
@@ -671,6 +812,14 @@ var webglRenderer = (options = {}) => ({
671
812
  subtree: true,
672
813
  characterData: true
673
814
  });
815
+ const geometryObserver = new MutationObserver(() => {
816
+ if (!ctx.isDragging()) paint();
817
+ wake();
818
+ });
819
+ geometryObserver.observe(element, {
820
+ attributes: true,
821
+ attributeFilter: ["style"]
822
+ });
674
823
  const refreshSurfaceStyle = () => {
675
824
  if (disposed) return;
676
825
  element.style.background = painted.background;
@@ -689,6 +838,7 @@ var webglRenderer = (options = {}) => ({
689
838
  paint();
690
839
  return () => {
691
840
  contentObserver.disconnect();
841
+ geometryObserver.disconnect();
692
842
  window.removeEventListener("resize", onResize);
693
843
  window.removeEventListener("scroll", onResize);
694
844
  teardown();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surdeddd/bottom-sheet",
3
- "version": "0.23.1",
3
+ "version": "0.23.2",
4
4
  "description": "Universal, headless bottom sheet — framework-agnostic core with React, Vue 3, Svelte, Solid, Qwik, Preact, Lit and Custom Element adapters. Pointer Events, snap points, momentum, a11y, SSR-safe.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -315,13 +315,13 @@
315
315
  {
316
316
  "name": "features (subpath)",
317
317
  "path": "dist/features.js",
318
- "limit": "2.72 KB",
318
+ "limit": "2.74 KB",
319
319
  "gzip": true
320
320
  },
321
321
  {
322
322
  "name": "webgl renderer (subpath)",
323
323
  "path": "dist/webgl.js",
324
- "limit": "5 KB",
324
+ "limit": "5.97 KB",
325
325
  "gzip": true
326
326
  }
327
327
  ],