@surdeddd/bottom-sheet 0.24.0 → 0.24.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/README.md CHANGED
@@ -9,7 +9,7 @@ HTML.
9
9
  [![npm](https://img.shields.io/npm/v/@surdeddd/bottom-sheet?style=flat-square&color=1a1614)](https://www.npmjs.com/package/@surdeddd/bottom-sheet)
10
10
  [![downloads](https://img.shields.io/npm/dm/@surdeddd/bottom-sheet?style=flat-square&color=1a1614)](https://www.npmjs.com/package/@surdeddd/bottom-sheet)
11
11
  ![core size](https://img.shields.io/badge/core-25.6%20KB%20gzip-1a1614?style=flat-square)
12
- ![tests](https://img.shields.io/badge/tests-1000%2B%20unit%20%C2%B7%20340%20e2e%20per%20engine-2ea44f?style=flat-square)
12
+ ![tests](https://img.shields.io/badge/tests-1000%2B%20unit%20%C2%B7%20370%20e2e%20per%20engine-2ea44f?style=flat-square)
13
13
  ![types](https://img.shields.io/badge/TypeScript-strict-3178c6?style=flat-square&logo=typescript&logoColor=white)
14
14
  ![license](https://img.shields.io/badge/license-MIT-dc3522?style=flat-square)
15
15
 
@@ -623,7 +623,7 @@ const sheet = new BottomSheetCore({
623
623
  | `/react` `/vue` `/svelte` `/solid` `/qwik` | 27 to 33 KB | The full engine plus the adapter |
624
624
  | `/element` | 33.3 KB | The full engine, the element and its styles |
625
625
  | `/overlay` | 8.4 KB | The standalone [overlay](docs/overlay.md) |
626
- | `/webgl` | 5.9 KB | Optional [GPU renderer](docs/webgl.md) for the sheet surface |
626
+ | `/webgl` | 6.4 KB | Experimental [GPU renderer](docs/webgl.md) for the sheet surface |
627
627
  | `/debug` | 0.9 KB | `attachDebugOverlay(engine)`: a live panel of the engine's state |
628
628
 
629
629
  An option whose feature is missing warns once in development and is ignored.
@@ -641,7 +641,7 @@ to `backdrop-filter` to WebGL, is detected and degrades.
641
641
  | | |
642
642
  | --- | --- |
643
643
  | Frameworks | [React](docs/react.md) · [Vue](docs/vue.md) · [Svelte](docs/svelte.md) · [Solid](docs/solid.md) · [Qwik](docs/qwik.md) · [Preact](docs/preact.md) · [Custom element](docs/web-component.md) · [Vanilla](docs/vanilla.md) · [Angular](docs/angular.md) · [Lit](docs/lit.md) · [Astro](docs/astro.md) |
644
- | Topics | [Theming](docs/theming.md) · [Anchors and scrim stages](docs/anchors.md) · [Content at every snap](docs/content-fit.md) · [Overlay](docs/overlay.md) · [RTL](docs/rtl.md) · [WebGL renderer](docs/webgl.md) · [Plugins](docs/plugins.md) |
644
+ | Topics | [Theming](docs/theming.md) · [Anchors and scrim stages](docs/anchors.md) · [Content at every snap](docs/content-fit.md) · [Overlay](docs/overlay.md) · [RTL](docs/rtl.md) · [WebGL renderer (experimental)](docs/webgl.md) · [Plugins](docs/plugins.md) |
645
645
  | Recipes | [Ten worked examples](docs/recipes/README.md): filter panel, checkout, comment thread, navigation drawer, mini player and more |
646
646
  | Examples | [Runnable projects](https://github.com/Surdeddd/BottomSheet/tree/main/examples) for React, Vue, Svelte, Solid, Lit, the custom element and vanilla |
647
647
  | Coming from another library | [From vaul or react-modal-sheet](docs/MIGRATION.md) |
package/README.ru.md CHANGED
@@ -9,7 +9,7 @@ Solid, Qwik, Preact** и под **веб-компонентом** `<bottom-sheet
9
9
  [![npm](https://img.shields.io/npm/v/@surdeddd/bottom-sheet?style=flat-square&color=1a1614)](https://www.npmjs.com/package/@surdeddd/bottom-sheet)
10
10
  [![downloads](https://img.shields.io/npm/dm/@surdeddd/bottom-sheet?style=flat-square&color=1a1614)](https://www.npmjs.com/package/@surdeddd/bottom-sheet)
11
11
  ![core size](https://img.shields.io/badge/core-25.6%20KB%20gzip-1a1614?style=flat-square)
12
- ![tests](https://img.shields.io/badge/tests-1000%2B%20unit%20%C2%B7%20340%20e2e%20per%20engine-2ea44f?style=flat-square)
12
+ ![tests](https://img.shields.io/badge/tests-1000%2B%20unit%20%C2%B7%20370%20e2e%20per%20engine-2ea44f?style=flat-square)
13
13
  ![types](https://img.shields.io/badge/TypeScript-strict-3178c6?style=flat-square&logo=typescript&logoColor=white)
14
14
  ![license](https://img.shields.io/badge/license-MIT-dc3522?style=flat-square)
15
15
 
@@ -637,7 +637,7 @@ const sheet = new BottomSheetCore({
637
637
  | `/react` `/vue` `/svelte` `/solid` `/qwik` | от 27 до 33 KB | Полный движок вместе с адаптером |
638
638
  | `/element` | 33.3 KB | Полный движок, элемент и его стили |
639
639
  | `/overlay` | 8.4 KB | Отдельный [оверлей](docs/overlay.md) |
640
- | `/webgl` | 5.9 KB | Необязательный [рендерер на GPU](docs/webgl.md) для поверхности шторки |
640
+ | `/webgl` | 6.4 KB | Экспериментальный [рендерер на GPU](docs/webgl.md) для поверхности шторки |
641
641
  | `/debug` | 0.9 KB | `attachDebugOverlay(engine)`: панель с состоянием движка в реальном времени |
642
642
 
643
643
  Опция, для которой не подключена возможность, один раз предупреждает в
@@ -656,7 +656,7 @@ Chrome и Edge 80, Safari 13.1 (iOS 13.4), Firefox 72. Эту границу з
656
656
  | | |
657
657
  | --- | --- |
658
658
  | Фреймворки | [React](docs/react.md) · [Vue](docs/vue.md) · [Svelte](docs/svelte.md) · [Solid](docs/solid.md) · [Qwik](docs/qwik.md) · [Preact](docs/preact.md) · [Веб-компонент](docs/web-component.md) · [Без фреймворка](docs/vanilla.md) · [Angular](docs/angular.md) · [Lit](docs/lit.md) · [Astro](docs/astro.md) |
659
- | Темы | [Стилизация](docs/theming.md) · [Якоря и сцены скрима](docs/anchors.md) · [Содержимое на каждой точке](docs/content-fit.md) · [Оверлей](docs/overlay.md) · [RTL](docs/rtl.md) · [Рендерер WebGL](docs/webgl.md) · [Плагины](docs/plugins.md) |
659
+ | Темы | [Стилизация](docs/theming.md) · [Якоря и сцены скрима](docs/anchors.md) · [Содержимое на каждой точке](docs/content-fit.md) · [Оверлей](docs/overlay.md) · [RTL](docs/rtl.md) · [Рендерер WebGL (экспериментальный)](docs/webgl.md) · [Плагины](docs/plugins.md) |
660
660
  | Рецепты | [Десять разобранных примеров](docs/recipes/README.md): панель фильтров, оформление заказа, тред комментариев, навигационная панель, мини-плеер и другие |
661
661
  | Примеры | [Запускаемые проекты](https://github.com/Surdeddd/BottomSheet/tree/main/examples) для React, Vue, Svelte, Solid, Lit, веб-компонента и движка без фреймворка |
662
662
  | Переход с другой библиотеки | [С vaul или react-modal-sheet](docs/MIGRATION.md) |
package/dist/webgl.cjs CHANGED
@@ -9,6 +9,22 @@ var supportsHighpFragment = (gl) => {
9
9
  return false;
10
10
  }
11
11
  };
12
+ var SOFTWARE = /swiftshader|llvmpipe|softpipe|software|basic render/i;
13
+ var MASKED = /^webkit webgl$/i;
14
+ var rendererName = (gl) => {
15
+ const plain = String(gl.getParameter(gl.RENDERER) ?? "");
16
+ if (plain && !MASKED.test(plain)) return plain;
17
+ const info = gl.getExtension("WEBGL_debug_renderer_info");
18
+ if (!info) return "";
19
+ return String(gl.getParameter(info.UNMASKED_RENDERER_WEBGL) ?? "");
20
+ };
21
+ var drawsOnCpu = (gl) => {
22
+ try {
23
+ return SOFTWARE.test(rendererName(gl));
24
+ } catch {
25
+ return false;
26
+ }
27
+ };
12
28
  var hasWebGL = () => {
13
29
  if (typeof document === "undefined") return false;
14
30
  if (typeof WebGLRenderingContext === "undefined" && typeof WebGL2RenderingContext === "undefined") {
@@ -34,14 +50,16 @@ var createGLHandle = (onLost) => {
34
50
  canvas.style.position = "fixed";
35
51
  canvas.style.left = "0";
36
52
  canvas.style.top = "0";
53
+ canvas.style.width = "100%";
54
+ canvas.style.height = "100%";
37
55
  canvas.style.pointerEvents = "none";
38
56
  const attrs = {
39
57
  alpha: true,
40
58
  premultipliedAlpha: true,
41
- antialias: true,
59
+ antialias: false,
42
60
  depth: false,
43
61
  stencil: false,
44
- preserveDrawingBuffer: false
62
+ preserveDrawingBuffer: true
45
63
  };
46
64
  let gl = null;
47
65
  let isWebGL2 = false;
@@ -85,8 +103,6 @@ var resizeDrawingBuffer = (handle, cssWidth, cssHeight, dpr) => {
85
103
  handle.canvas.width = w;
86
104
  handle.canvas.height = h;
87
105
  }
88
- handle.canvas.style.width = `${cssWidth}px`;
89
- handle.canvas.style.height = `${cssHeight}px`;
90
106
  handle.gl.viewport(0, 0, w, h);
91
107
  };
92
108
 
@@ -102,7 +118,7 @@ precision ${precision} float;
102
118
 
103
119
  uniform vec2 u_resolution;
104
120
  uniform vec4 u_rect;
105
- uniform float u_radius;
121
+ uniform vec4 u_radius;
106
122
  uniform vec4 u_color;
107
123
  uniform float u_shadow;
108
124
  uniform float u_bend;
@@ -115,7 +131,9 @@ uniform sampler2D u_scroll;
115
131
  uniform vec4 u_scrollRect;
116
132
  uniform vec2 u_scrollMap;
117
133
 
118
- float roundedBoxSDF(vec2 p, vec2 halfSize, float r) {
134
+ float roundedBoxSDF(vec2 p, vec2 halfSize, vec4 radii) {
135
+ vec2 side = p.x < 0.0 ? radii.xw : radii.yz;
136
+ float r = min(p.y < 0.0 ? side.x : side.y, min(halfSize.x, halfSize.y));
119
137
  vec2 q = abs(p) - halfSize + r;
120
138
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
121
139
  }
@@ -131,8 +149,7 @@ void main() {
131
149
  vec2 p = frag - center;
132
150
  p.y -= bend;
133
151
 
134
- float r = min(u_radius, min(halfSize.x, halfSize.y));
135
- float d = roundedBoxSDF(p, halfSize, r);
152
+ float d = roundedBoxSDF(p, halfSize, u_radius);
136
153
  float surface = 1.0 - smoothstep(-1.0, 1.0, d);
137
154
 
138
155
  vec2 uv = (p + halfSize) / u_rect.zw;
@@ -256,7 +273,14 @@ var createSurfaceProgram = (gl, highp = true) => {
256
273
  frame.width * dpr,
257
274
  frame.height * dpr
258
275
  );
259
- gl.uniform1f(uRadius, frame.radius * dpr);
276
+ const [topLeft, topRight, bottomRight, bottomLeft] = frame.radii;
277
+ gl.uniform4f(
278
+ uRadius,
279
+ topLeft * dpr,
280
+ topRight * dpr,
281
+ bottomRight * dpr,
282
+ bottomLeft * dpr
283
+ );
260
284
  gl.uniform4f(uColor, ...frame.color);
261
285
  gl.uniform1f(uShadow, frame.shadow);
262
286
  gl.uniform1f(uBend, state.bend * dpr);
@@ -616,6 +640,7 @@ var parseColor = (value) => {
616
640
  }
617
641
  return readColor(value);
618
642
  };
643
+ var parentAcrossShadow = (node) => node.parentElement ?? node.getRootNode().host ?? null;
619
644
  var parseRadius = (value) => {
620
645
  const n = parseFloat(value);
621
646
  return Number.isFinite(n) ? n : 0;
@@ -661,7 +686,12 @@ var webglRenderer = (options = {}) => ({
661
686
  const cs = getComputedStyle(element);
662
687
  return {
663
688
  color: parseColor(cs.backgroundColor),
664
- radius: parseRadius(cs.borderTopLeftRadius)
689
+ radii: [
690
+ parseRadius(cs.borderTopLeftRadius),
691
+ parseRadius(cs.borderTopRightRadius),
692
+ parseRadius(cs.borderBottomRightRadius),
693
+ parseRadius(cs.borderBottomLeftRadius)
694
+ ]
665
695
  };
666
696
  };
667
697
  let surfaceStyle = readSurfaceStyle();
@@ -696,6 +726,12 @@ var webglRenderer = (options = {}) => ({
696
726
  bail("no-webgl");
697
727
  return;
698
728
  }
729
+ if (!options.allowSoftware && drawsOnCpu(handle.gl)) {
730
+ handle.destroy();
731
+ handle = null;
732
+ bail("software-renderer");
733
+ return;
734
+ }
699
735
  program = createSurfaceProgram(handle.gl, handle.highp);
700
736
  if (!program) {
701
737
  handle.destroy();
@@ -706,30 +742,22 @@ var webglRenderer = (options = {}) => ({
706
742
  const parent = element.parentElement ?? document.body;
707
743
  parent.insertBefore(handle.canvas, element);
708
744
  let canvasZ = "";
745
+ let canvasPosition = "fixed";
709
746
  const followStack = () => {
710
747
  if (!handle) return;
711
- const elementZ = getComputedStyle(element).zIndex;
712
- const next = elementZ && elementZ !== "auto" ? String(Number(elementZ) - 1) : "0";
748
+ const cs = getComputedStyle(element);
749
+ const position = cs.position === "absolute" ? "absolute" : "fixed";
750
+ if (position !== canvasPosition) {
751
+ canvasPosition = position;
752
+ handle.canvas.style.position = position;
753
+ }
754
+ const next = cs.zIndex && cs.zIndex !== "auto" ? String(Number(cs.zIndex) - 1) : "0";
713
755
  if (next === canvasZ) return;
714
756
  canvasZ = next;
715
757
  handle.canvas.style.zIndex = next;
716
758
  };
717
759
  followStack();
718
760
  suppressDomPaint();
719
- const readFrame = () => {
720
- if (!handle) return null;
721
- const rect = element.getBoundingClientRect();
722
- if (rect.width <= 0 || rect.height <= 0) return null;
723
- return {
724
- x: rect.left,
725
- y: rect.top,
726
- width: rect.width,
727
- height: rect.height,
728
- radius: surfaceStyle.radius,
729
- color: surfaceStyle.color,
730
- shadow: shadowStrength
731
- };
732
- };
733
761
  const dprOf = () => options.dpr ?? window.devicePixelRatio ?? 1;
734
762
  const releaseCapture = () => {
735
763
  releaseText?.();
@@ -753,41 +781,52 @@ var webglRenderer = (options = {}) => ({
753
781
  capture = next;
754
782
  releaseText = hideCapturedText(next.hiddenNodes);
755
783
  };
756
- const scrollLayer = (frame) => {
784
+ const scrollLayer = (sheet) => {
757
785
  const scroll = capture?.scroll;
758
786
  const scroller = ctx.scrollContainer;
759
787
  if (!scroll || !scroller) return null;
760
788
  const box = scroller.getBoundingClientRect();
761
- const left = box.left + scroller.clientLeft - frame.x;
762
- const top = box.top + scroller.clientTop - frame.y;
789
+ const left = box.left + scroller.clientLeft - sheet.left;
790
+ const top = box.top + scroller.clientTop - sheet.top;
763
791
  return {
764
792
  texture: scroll.texture,
765
793
  rect: [
766
- left / frame.width,
767
- top / frame.height,
768
- (left + scroller.clientWidth) / frame.width,
769
- (top + scroller.clientHeight) / frame.height
794
+ left / sheet.width,
795
+ top / sheet.height,
796
+ (left + scroller.clientWidth) / sheet.width,
797
+ (top + scroller.clientHeight) / sheet.height
770
798
  ],
771
799
  map: [
772
- frame.height / scroll.height,
800
+ sheet.height / scroll.height,
773
801
  (scroll.above + scroller.scrollTop - scroll.top) / scroll.height
774
802
  ]
775
803
  };
776
804
  };
777
805
  const paint = () => {
778
806
  if (disposed || !handle || !program) return;
779
- const frame = readFrame();
780
- if (!frame) return;
807
+ const sheet = element.getBoundingClientRect();
808
+ const area = handle.canvas.getBoundingClientRect();
809
+ if (sheet.width <= 0 || sheet.height <= 0) return;
810
+ if (area.width <= 0 || area.height <= 0) return;
781
811
  const dpr = dprOf();
782
- resizeDrawingBuffer(handle, window.innerWidth, window.innerHeight, dpr);
812
+ resizeDrawingBuffer(handle, area.width, area.height, dpr);
783
813
  const motion = Math.min(1, Math.abs(bend) / MAX_BEND_PX);
814
+ const frame = {
815
+ x: sheet.left - area.left,
816
+ y: sheet.top - area.top,
817
+ width: sheet.width,
818
+ height: sheet.height,
819
+ radii: surfaceStyle.radii,
820
+ color: surfaceStyle.color,
821
+ shadow: shadowStrength
822
+ };
784
823
  program.draw(frame, {
785
824
  bend,
786
825
  dpr,
787
826
  sheen: sheenStrength,
788
827
  glass: glassStrength * motion,
789
828
  content: capture?.texture ?? null,
790
- scroll: scrollLayer(frame)
829
+ scroll: scrollLayer(sheet)
791
830
  });
792
831
  };
793
832
  const tick = () => {
@@ -806,6 +845,7 @@ var webglRenderer = (options = {}) => ({
806
845
  paint();
807
846
  running = false;
808
847
  frameId = null;
848
+ queueRefresh();
809
849
  return;
810
850
  }
811
851
  frameId = requestAnimationFrame(tick);
@@ -844,36 +884,71 @@ var webglRenderer = (options = {}) => ({
844
884
  subtree: true,
845
885
  characterData: true
846
886
  });
847
- const geometryObserver = new MutationObserver(() => {
887
+ const onGeometry = () => {
848
888
  followStack();
849
889
  if (!ctx.isDragging()) paint();
850
890
  wake();
851
- });
891
+ };
892
+ const geometryObserver = new MutationObserver(onGeometry);
852
893
  geometryObserver.observe(element, {
853
894
  attributes: true,
854
895
  attributeFilter: ["style"]
855
896
  });
856
897
  const refreshSurfaceStyle = () => {
857
- if (disposed) return;
858
- element.style.background = painted.background;
859
- element.style.boxShadow = painted.boxShadow;
860
- surfaceStyle = readSurfaceStyle();
861
- element.style.background = "transparent";
862
- element.style.boxShadow = "none";
898
+ const moved = geometryObserver.takeRecords().length > 0;
899
+ const style = element.style;
900
+ const transition = style.transition;
901
+ style.transition = "none";
902
+ restoreDom();
903
+ const next = readSurfaceStyle();
904
+ suppressDomPaint();
905
+ void getComputedStyle(element).backgroundColor;
906
+ style.transition = transition;
907
+ geometryObserver.takeRecords();
908
+ const changed = next.radii.some((radius, i) => radius !== surfaceStyle.radii[i]) || next.color.some((channel, i) => channel !== surfaceStyle.color[i]);
909
+ surfaceStyle = next;
910
+ if (!moved) {
911
+ followStack();
912
+ return changed;
913
+ }
914
+ onGeometry();
915
+ return false;
916
+ };
917
+ let refreshQueued = false;
918
+ const queueRefresh = () => {
919
+ if (disposed || refreshQueued) return;
920
+ refreshQueued = true;
921
+ requestAnimationFrame(() => {
922
+ refreshQueued = false;
923
+ if (!disposed && refreshSurfaceStyle()) paint();
924
+ });
863
925
  };
926
+ const onThemeSignal = () => {
927
+ if (!ctx.isDragging() && !ctx.isAnimating()) queueRefresh();
928
+ };
929
+ const themeObserver = new MutationObserver(onThemeSignal);
930
+ themeObserver.observe(element, {
931
+ attributes: true,
932
+ attributeFilter: ["class"]
933
+ });
934
+ for (let node = parentAcrossShadow(element); node; node = parentAcrossShadow(node)) {
935
+ themeObserver.observe(node, { attributes: true });
936
+ }
937
+ const scheme = typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
938
+ scheme?.addEventListener?.("change", onThemeSignal);
864
939
  const onResize = () => {
865
940
  if (disposed) return;
866
941
  refreshSurfaceStyle();
867
942
  paint();
868
943
  };
869
944
  window.addEventListener("resize", onResize, { passive: true });
870
- window.addEventListener("scroll", onResize, { passive: true });
871
945
  paint();
872
946
  return () => {
873
947
  contentObserver.disconnect();
874
948
  geometryObserver.disconnect();
949
+ themeObserver.disconnect();
950
+ scheme?.removeEventListener?.("change", onThemeSignal);
875
951
  window.removeEventListener("resize", onResize);
876
- window.removeEventListener("scroll", onResize);
877
952
  teardown();
878
953
  };
879
954
  }
package/dist/webgl.d.cts CHANGED
@@ -7,9 +7,10 @@ type WebGLRendererOptions = {
7
7
  glass?: number;
8
8
  liftContent?: boolean;
9
9
  dpr?: number;
10
+ allowSoftware?: boolean;
10
11
  onUnsupported?: (reason: WebGLUnsupportedReason) => void;
11
12
  };
12
- type WebGLUnsupportedReason = "no-document" | "no-webgl" | "reduced-motion" | "context-lost";
13
+ type WebGLUnsupportedReason = "no-document" | "no-webgl" | "software-renderer" | "reduced-motion" | "context-lost";
13
14
 
14
15
  declare const webglRenderer: (options?: WebGLRendererOptions) => EngineFeature;
15
16
 
package/dist/webgl.d.ts CHANGED
@@ -7,9 +7,10 @@ type WebGLRendererOptions = {
7
7
  glass?: number;
8
8
  liftContent?: boolean;
9
9
  dpr?: number;
10
+ allowSoftware?: boolean;
10
11
  onUnsupported?: (reason: WebGLUnsupportedReason) => void;
11
12
  };
12
- type WebGLUnsupportedReason = "no-document" | "no-webgl" | "reduced-motion" | "context-lost";
13
+ type WebGLUnsupportedReason = "no-document" | "no-webgl" | "software-renderer" | "reduced-motion" | "context-lost";
13
14
 
14
15
  declare const webglRenderer: (options?: WebGLRendererOptions) => EngineFeature;
15
16
 
package/dist/webgl.js CHANGED
@@ -7,6 +7,22 @@ var supportsHighpFragment = (gl) => {
7
7
  return false;
8
8
  }
9
9
  };
10
+ var SOFTWARE = /swiftshader|llvmpipe|softpipe|software|basic render/i;
11
+ var MASKED = /^webkit webgl$/i;
12
+ var rendererName = (gl) => {
13
+ const plain = String(gl.getParameter(gl.RENDERER) ?? "");
14
+ if (plain && !MASKED.test(plain)) return plain;
15
+ const info = gl.getExtension("WEBGL_debug_renderer_info");
16
+ if (!info) return "";
17
+ return String(gl.getParameter(info.UNMASKED_RENDERER_WEBGL) ?? "");
18
+ };
19
+ var drawsOnCpu = (gl) => {
20
+ try {
21
+ return SOFTWARE.test(rendererName(gl));
22
+ } catch {
23
+ return false;
24
+ }
25
+ };
10
26
  var hasWebGL = () => {
11
27
  if (typeof document === "undefined") return false;
12
28
  if (typeof WebGLRenderingContext === "undefined" && typeof WebGL2RenderingContext === "undefined") {
@@ -32,14 +48,16 @@ var createGLHandle = (onLost) => {
32
48
  canvas.style.position = "fixed";
33
49
  canvas.style.left = "0";
34
50
  canvas.style.top = "0";
51
+ canvas.style.width = "100%";
52
+ canvas.style.height = "100%";
35
53
  canvas.style.pointerEvents = "none";
36
54
  const attrs = {
37
55
  alpha: true,
38
56
  premultipliedAlpha: true,
39
- antialias: true,
57
+ antialias: false,
40
58
  depth: false,
41
59
  stencil: false,
42
- preserveDrawingBuffer: false
60
+ preserveDrawingBuffer: true
43
61
  };
44
62
  let gl = null;
45
63
  let isWebGL2 = false;
@@ -83,8 +101,6 @@ var resizeDrawingBuffer = (handle, cssWidth, cssHeight, dpr) => {
83
101
  handle.canvas.width = w;
84
102
  handle.canvas.height = h;
85
103
  }
86
- handle.canvas.style.width = `${cssWidth}px`;
87
- handle.canvas.style.height = `${cssHeight}px`;
88
104
  handle.gl.viewport(0, 0, w, h);
89
105
  };
90
106
 
@@ -100,7 +116,7 @@ precision ${precision} float;
100
116
 
101
117
  uniform vec2 u_resolution;
102
118
  uniform vec4 u_rect;
103
- uniform float u_radius;
119
+ uniform vec4 u_radius;
104
120
  uniform vec4 u_color;
105
121
  uniform float u_shadow;
106
122
  uniform float u_bend;
@@ -113,7 +129,9 @@ uniform sampler2D u_scroll;
113
129
  uniform vec4 u_scrollRect;
114
130
  uniform vec2 u_scrollMap;
115
131
 
116
- float roundedBoxSDF(vec2 p, vec2 halfSize, float r) {
132
+ float roundedBoxSDF(vec2 p, vec2 halfSize, vec4 radii) {
133
+ vec2 side = p.x < 0.0 ? radii.xw : radii.yz;
134
+ float r = min(p.y < 0.0 ? side.x : side.y, min(halfSize.x, halfSize.y));
117
135
  vec2 q = abs(p) - halfSize + r;
118
136
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
119
137
  }
@@ -129,8 +147,7 @@ void main() {
129
147
  vec2 p = frag - center;
130
148
  p.y -= bend;
131
149
 
132
- float r = min(u_radius, min(halfSize.x, halfSize.y));
133
- float d = roundedBoxSDF(p, halfSize, r);
150
+ float d = roundedBoxSDF(p, halfSize, u_radius);
134
151
  float surface = 1.0 - smoothstep(-1.0, 1.0, d);
135
152
 
136
153
  vec2 uv = (p + halfSize) / u_rect.zw;
@@ -254,7 +271,14 @@ var createSurfaceProgram = (gl, highp = true) => {
254
271
  frame.width * dpr,
255
272
  frame.height * dpr
256
273
  );
257
- gl.uniform1f(uRadius, frame.radius * dpr);
274
+ const [topLeft, topRight, bottomRight, bottomLeft] = frame.radii;
275
+ gl.uniform4f(
276
+ uRadius,
277
+ topLeft * dpr,
278
+ topRight * dpr,
279
+ bottomRight * dpr,
280
+ bottomLeft * dpr
281
+ );
258
282
  gl.uniform4f(uColor, ...frame.color);
259
283
  gl.uniform1f(uShadow, frame.shadow);
260
284
  gl.uniform1f(uBend, state.bend * dpr);
@@ -614,6 +638,7 @@ var parseColor = (value) => {
614
638
  }
615
639
  return readColor(value);
616
640
  };
641
+ var parentAcrossShadow = (node) => node.parentElement ?? node.getRootNode().host ?? null;
617
642
  var parseRadius = (value) => {
618
643
  const n = parseFloat(value);
619
644
  return Number.isFinite(n) ? n : 0;
@@ -659,7 +684,12 @@ var webglRenderer = (options = {}) => ({
659
684
  const cs = getComputedStyle(element);
660
685
  return {
661
686
  color: parseColor(cs.backgroundColor),
662
- radius: parseRadius(cs.borderTopLeftRadius)
687
+ radii: [
688
+ parseRadius(cs.borderTopLeftRadius),
689
+ parseRadius(cs.borderTopRightRadius),
690
+ parseRadius(cs.borderBottomRightRadius),
691
+ parseRadius(cs.borderBottomLeftRadius)
692
+ ]
663
693
  };
664
694
  };
665
695
  let surfaceStyle = readSurfaceStyle();
@@ -694,6 +724,12 @@ var webglRenderer = (options = {}) => ({
694
724
  bail("no-webgl");
695
725
  return;
696
726
  }
727
+ if (!options.allowSoftware && drawsOnCpu(handle.gl)) {
728
+ handle.destroy();
729
+ handle = null;
730
+ bail("software-renderer");
731
+ return;
732
+ }
697
733
  program = createSurfaceProgram(handle.gl, handle.highp);
698
734
  if (!program) {
699
735
  handle.destroy();
@@ -704,30 +740,22 @@ var webglRenderer = (options = {}) => ({
704
740
  const parent = element.parentElement ?? document.body;
705
741
  parent.insertBefore(handle.canvas, element);
706
742
  let canvasZ = "";
743
+ let canvasPosition = "fixed";
707
744
  const followStack = () => {
708
745
  if (!handle) return;
709
- const elementZ = getComputedStyle(element).zIndex;
710
- const next = elementZ && elementZ !== "auto" ? String(Number(elementZ) - 1) : "0";
746
+ const cs = getComputedStyle(element);
747
+ const position = cs.position === "absolute" ? "absolute" : "fixed";
748
+ if (position !== canvasPosition) {
749
+ canvasPosition = position;
750
+ handle.canvas.style.position = position;
751
+ }
752
+ const next = cs.zIndex && cs.zIndex !== "auto" ? String(Number(cs.zIndex) - 1) : "0";
711
753
  if (next === canvasZ) return;
712
754
  canvasZ = next;
713
755
  handle.canvas.style.zIndex = next;
714
756
  };
715
757
  followStack();
716
758
  suppressDomPaint();
717
- const readFrame = () => {
718
- if (!handle) return null;
719
- const rect = element.getBoundingClientRect();
720
- if (rect.width <= 0 || rect.height <= 0) return null;
721
- return {
722
- x: rect.left,
723
- y: rect.top,
724
- width: rect.width,
725
- height: rect.height,
726
- radius: surfaceStyle.radius,
727
- color: surfaceStyle.color,
728
- shadow: shadowStrength
729
- };
730
- };
731
759
  const dprOf = () => options.dpr ?? window.devicePixelRatio ?? 1;
732
760
  const releaseCapture = () => {
733
761
  releaseText?.();
@@ -751,41 +779,52 @@ var webglRenderer = (options = {}) => ({
751
779
  capture = next;
752
780
  releaseText = hideCapturedText(next.hiddenNodes);
753
781
  };
754
- const scrollLayer = (frame) => {
782
+ const scrollLayer = (sheet) => {
755
783
  const scroll = capture?.scroll;
756
784
  const scroller = ctx.scrollContainer;
757
785
  if (!scroll || !scroller) return null;
758
786
  const box = scroller.getBoundingClientRect();
759
- const left = box.left + scroller.clientLeft - frame.x;
760
- const top = box.top + scroller.clientTop - frame.y;
787
+ const left = box.left + scroller.clientLeft - sheet.left;
788
+ const top = box.top + scroller.clientTop - sheet.top;
761
789
  return {
762
790
  texture: scroll.texture,
763
791
  rect: [
764
- left / frame.width,
765
- top / frame.height,
766
- (left + scroller.clientWidth) / frame.width,
767
- (top + scroller.clientHeight) / frame.height
792
+ left / sheet.width,
793
+ top / sheet.height,
794
+ (left + scroller.clientWidth) / sheet.width,
795
+ (top + scroller.clientHeight) / sheet.height
768
796
  ],
769
797
  map: [
770
- frame.height / scroll.height,
798
+ sheet.height / scroll.height,
771
799
  (scroll.above + scroller.scrollTop - scroll.top) / scroll.height
772
800
  ]
773
801
  };
774
802
  };
775
803
  const paint = () => {
776
804
  if (disposed || !handle || !program) return;
777
- const frame = readFrame();
778
- if (!frame) return;
805
+ const sheet = element.getBoundingClientRect();
806
+ const area = handle.canvas.getBoundingClientRect();
807
+ if (sheet.width <= 0 || sheet.height <= 0) return;
808
+ if (area.width <= 0 || area.height <= 0) return;
779
809
  const dpr = dprOf();
780
- resizeDrawingBuffer(handle, window.innerWidth, window.innerHeight, dpr);
810
+ resizeDrawingBuffer(handle, area.width, area.height, dpr);
781
811
  const motion = Math.min(1, Math.abs(bend) / MAX_BEND_PX);
812
+ const frame = {
813
+ x: sheet.left - area.left,
814
+ y: sheet.top - area.top,
815
+ width: sheet.width,
816
+ height: sheet.height,
817
+ radii: surfaceStyle.radii,
818
+ color: surfaceStyle.color,
819
+ shadow: shadowStrength
820
+ };
782
821
  program.draw(frame, {
783
822
  bend,
784
823
  dpr,
785
824
  sheen: sheenStrength,
786
825
  glass: glassStrength * motion,
787
826
  content: capture?.texture ?? null,
788
- scroll: scrollLayer(frame)
827
+ scroll: scrollLayer(sheet)
789
828
  });
790
829
  };
791
830
  const tick = () => {
@@ -804,6 +843,7 @@ var webglRenderer = (options = {}) => ({
804
843
  paint();
805
844
  running = false;
806
845
  frameId = null;
846
+ queueRefresh();
807
847
  return;
808
848
  }
809
849
  frameId = requestAnimationFrame(tick);
@@ -842,36 +882,71 @@ var webglRenderer = (options = {}) => ({
842
882
  subtree: true,
843
883
  characterData: true
844
884
  });
845
- const geometryObserver = new MutationObserver(() => {
885
+ const onGeometry = () => {
846
886
  followStack();
847
887
  if (!ctx.isDragging()) paint();
848
888
  wake();
849
- });
889
+ };
890
+ const geometryObserver = new MutationObserver(onGeometry);
850
891
  geometryObserver.observe(element, {
851
892
  attributes: true,
852
893
  attributeFilter: ["style"]
853
894
  });
854
895
  const refreshSurfaceStyle = () => {
855
- if (disposed) return;
856
- element.style.background = painted.background;
857
- element.style.boxShadow = painted.boxShadow;
858
- surfaceStyle = readSurfaceStyle();
859
- element.style.background = "transparent";
860
- element.style.boxShadow = "none";
896
+ const moved = geometryObserver.takeRecords().length > 0;
897
+ const style = element.style;
898
+ const transition = style.transition;
899
+ style.transition = "none";
900
+ restoreDom();
901
+ const next = readSurfaceStyle();
902
+ suppressDomPaint();
903
+ void getComputedStyle(element).backgroundColor;
904
+ style.transition = transition;
905
+ geometryObserver.takeRecords();
906
+ const changed = next.radii.some((radius, i) => radius !== surfaceStyle.radii[i]) || next.color.some((channel, i) => channel !== surfaceStyle.color[i]);
907
+ surfaceStyle = next;
908
+ if (!moved) {
909
+ followStack();
910
+ return changed;
911
+ }
912
+ onGeometry();
913
+ return false;
914
+ };
915
+ let refreshQueued = false;
916
+ const queueRefresh = () => {
917
+ if (disposed || refreshQueued) return;
918
+ refreshQueued = true;
919
+ requestAnimationFrame(() => {
920
+ refreshQueued = false;
921
+ if (!disposed && refreshSurfaceStyle()) paint();
922
+ });
861
923
  };
924
+ const onThemeSignal = () => {
925
+ if (!ctx.isDragging() && !ctx.isAnimating()) queueRefresh();
926
+ };
927
+ const themeObserver = new MutationObserver(onThemeSignal);
928
+ themeObserver.observe(element, {
929
+ attributes: true,
930
+ attributeFilter: ["class"]
931
+ });
932
+ for (let node = parentAcrossShadow(element); node; node = parentAcrossShadow(node)) {
933
+ themeObserver.observe(node, { attributes: true });
934
+ }
935
+ const scheme = typeof matchMedia === "function" ? matchMedia("(prefers-color-scheme: dark)") : null;
936
+ scheme?.addEventListener?.("change", onThemeSignal);
862
937
  const onResize = () => {
863
938
  if (disposed) return;
864
939
  refreshSurfaceStyle();
865
940
  paint();
866
941
  };
867
942
  window.addEventListener("resize", onResize, { passive: true });
868
- window.addEventListener("scroll", onResize, { passive: true });
869
943
  paint();
870
944
  return () => {
871
945
  contentObserver.disconnect();
872
946
  geometryObserver.disconnect();
947
+ themeObserver.disconnect();
948
+ scheme?.removeEventListener?.("change", onThemeSignal);
873
949
  window.removeEventListener("resize", onResize);
874
- window.removeEventListener("scroll", onResize);
875
950
  teardown();
876
951
  };
877
952
  }
package/llms.txt CHANGED
@@ -21,7 +21,7 @@ This file is a complete reference written for AI coding assistants. Every name b
21
21
  | `@surdeddd/bottom-sheet/overlay` | `OverlayEngine`: a two-state slide-in panel without snap points |
22
22
  | `@surdeddd/bottom-sheet/core` | `BottomSheetCore`: the engine without optional features |
23
23
  | `@surdeddd/bottom-sheet/features` | `routeFeature`, `visualViewportFeature`, `contentSwipeFeature`, `contentFitFeature`, `persistFeature`, `autoCollapseFeature` |
24
- | `@surdeddd/bottom-sheet/webgl` | `webglRenderer`: optional GPU renderer |
24
+ | `@surdeddd/bottom-sheet/webgl` | `webglRenderer`: experimental GPU renderer; do not add it unless asked |
25
25
  | `@surdeddd/bottom-sheet/debug` | `attachDebugOverlay(engine)` |
26
26
  | `@surdeddd/bottom-sheet/integrations/react-hook-form` | `useFormGuard(sheetRef, form)` |
27
27
  | `@surdeddd/bottom-sheet/integrations/formik` | `useFormikGuard(sheetRef, formikRef)` |
@@ -323,7 +323,7 @@ Class names: `bs-root`, `bs-backdrop`, `bs-screen`, `bs-sheet`, `bs-handle`, `bs
323
323
 
324
324
  - [Architecture](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/ARCHITECTURE.md)
325
325
  - [API stability](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/api-stability.md)
326
- - [WebGL renderer](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/webgl.md)
326
+ - [WebGL renderer (experimental)](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/webgl.md)
327
327
  - [Plugins](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/plugins.md)
328
328
  - [RTL](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/rtl.md)
329
329
  - [Browser support](https://raw.githubusercontent.com/Surdeddd/BottomSheet/main/docs/browser-support.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surdeddd/bottom-sheet",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "description": "Bottom sheet, drawer and modal sheet with snap points, spring physics and touch gestures. One engine for React, Vue 3, Svelte 5, Solid, Qwik, Preact and a <bottom-sheet> web component. Accessible, SSR-safe, no dependencies.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -340,7 +340,7 @@
340
340
  {
341
341
  "name": "webgl renderer (subpath)",
342
342
  "path": "dist/webgl.js",
343
- "limit": "5.97 KB",
343
+ "limit": "6.44 KB",
344
344
  "gzip": true
345
345
  }
346
346
  ],