@underverse-ui/underverse 1.0.33 → 1.0.34

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/AGENTS.md CHANGED
@@ -51,7 +51,11 @@ import { OverlayScrollbarProvider } from "@underverse-ui/underverse";
51
51
  export function App() {
52
52
  return (
53
53
  <>
54
- <OverlayScrollbarProvider enabled theme="os-theme-underverse" autoHide="leave" />
54
+ <OverlayScrollbarProvider
55
+ enabled
56
+ theme="os-theme-underverse"
57
+ autoHide="leave"
58
+ />
55
59
  {/* app */}
56
60
  </>
57
61
  );
@@ -60,8 +64,8 @@ export function App() {
60
64
 
61
65
  Behavior:
62
66
 
63
- - Provider initializes only on `[data-os-scrollbar]`.
64
- - Underverse components already mark their internal scroll containers.
67
+ - Provider initializes globally by default on common scroll selectors (`.overflow-*`, `textarea`) and `[data-os-scrollbar]`.
68
+ - Provider can run globally via custom `selector` (for example: `.overflow-auto, .overflow-y-auto, .overflow-x-auto, [data-os-scrollbar]`).
65
69
  - Use `data-os-ignore` on a node to opt out.
66
70
 
67
71
  ## i18n Notes
package/CHANGELOG.md ADDED
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@underverse-ui/underverse` are documented in this file.
4
+
5
+ ## [1.0.32] - 2026-02-24
6
+
7
+ ### Changed
8
+
9
+ - Standardized OverlayScrollbars initialization to explicit marker targeting only:
10
+ - Provider now initializes only on `[data-os-scrollbar]`.
11
+ - Removed generic overflow class scanning (`.overflow-*`).
12
+ - Hardened provider behavior for production:
13
+ - No initialization on `document.body` / `document.documentElement`.
14
+ - Excludes portal / modal / toast trees:
15
+ - `[data-radix-portal]`
16
+ - `[role="dialog"]`
17
+ - `[aria-modal="true"]`
18
+ - `[data-sonner-toaster]`
19
+ - Supports node-level opt-out with `data-os-ignore`.
20
+ - Added provider configuration props:
21
+ - `enabled` (default `true`)
22
+ - `theme` (default `os-theme-underverse`)
23
+ - `visibility`
24
+ - `autoHide`
25
+ - `autoHideDelay`
26
+ - `dragScroll`
27
+ - `clickScroll`
28
+ - `selector` (default `.overflow-auto, .overflow-y-auto, .overflow-x-auto, .overflow-scroll, .overflow-y-scroll, .overflow-x-scroll, textarea, [data-os-scrollbar]`)
29
+ - `exclude` (default `html, body, [data-os-ignore], [data-radix-portal], [role='dialog'], [aria-modal='true'], [data-sonner-toaster]`)
30
+ - Exported provider prop type:
31
+ - `OverlayScrollbarProviderProps`
32
+
33
+ ### Updated Components
34
+
35
+ - Provider now covers common scrollable surfaces by default via global selector, so Underverse components do not require per-component manual marker wiring.
36
+
37
+ ### Internal
38
+
39
+ - `Popover` now sets `role="dialog"` only when `modal=true`, avoiding accidental exclusion for non-modal popovers.
40
+ - Moved to default global selector behavior, so apps no longer need to add `data-os-scrollbar` manually to each Underverse component.
41
+
42
+ ### Testing
43
+
44
+ - Added controller-level tests for:
45
+ - selector initialization
46
+ - exclude behavior
47
+ - dynamic add/remove cleanup
48
+ - portal safety with wide selectors
49
+ - destroy cleanup (memory leak prevention)
50
+
51
+ ### Migration
52
+
53
+ - Mount a single `OverlayScrollbarProvider` from the package at app root.
54
+ - Remove app-local DOM-scanning scrollbar providers.
55
+ - Keep `overlayscrollbars/overlayscrollbars.css` imported globally.
56
+ - Default is already global selector mode. Override `selector` only if you need custom scope.
57
+ - For app-specific opt-out nodes, use `data-os-ignore`.
package/README.md CHANGED
@@ -138,6 +138,7 @@ import { Form, FormField, FormItem, FormLabel, FormMessage } from "@underverse-u
138
138
  ### Overlay Scrollbars (Optional, Recommended)
139
139
 
140
140
  Use `OverlayScrollbarProvider` to get overlay scrollbars (no layout space taken) across your app and Underverse components.
141
+ See release notes in `CHANGELOG.md` for migration details by version.
141
142
 
142
143
  ```tsx
143
144
  import "overlayscrollbars/overlayscrollbars.css";
@@ -160,16 +161,37 @@ function App() {
160
161
 
161
162
  Provider behavior:
162
163
 
163
- - Initializes **only** on elements marked with `data-os-scrollbar`.
164
+ - Initializes globally by default on common scroll containers (`.overflow-*`, `textarea`) and `[data-os-scrollbar]`.
164
165
  - Does **not** initialize on `document.body` / `document.documentElement`.
165
166
  - Skips portal/dialog trees (`[data-radix-portal]`, `[role="dialog"]`, `[aria-modal="true"]`, `[data-sonner-toaster]`).
166
167
  - Per-node opt-out is available via `data-os-ignore`.
167
168
 
169
+ Provider props:
170
+
171
+ - `enabled?: boolean`
172
+ - `theme?: string`
173
+ - `visibility?: "visible" | "hidden" | "auto"`
174
+ - `autoHide?: "never" | "scroll" | "leave" | "move"`
175
+ - `autoHideDelay?: number`
176
+ - `dragScroll?: boolean`
177
+ - `clickScroll?: boolean`
178
+ - `selector?: string` default: `.overflow-auto, .overflow-y-auto, .overflow-x-auto, .overflow-scroll, .overflow-y-scroll, .overflow-x-scroll, textarea, [data-os-scrollbar]`
179
+ - `exclude?: string` default: `html, body, [data-os-ignore], [data-radix-portal], [role='dialog'], [aria-modal='true'], [data-sonner-toaster]`
180
+
181
+ Custom selector mode example:
182
+
183
+ ```tsx
184
+ <OverlayScrollbarProvider
185
+ selector=".overflow-auto, .overflow-y-auto, .overflow-x-auto, [data-os-scrollbar]"
186
+ />
187
+ ```
188
+
168
189
  Migration notes:
169
190
 
170
191
  - Remove any local DOM-scanning scrollbar provider in your app.
171
192
  - Keep a single `OverlayScrollbarProvider` mounted once at app root.
172
- - If you have custom app-level scroll containers outside Underverse components, add `data-os-scrollbar` to those nodes.
193
+ - You no longer need to add `data-os-scrollbar` to every Underverse component manually.
194
+ - Use `data-os-scrollbar` only for custom scroll nodes that are not covered by your selector.
173
195
 
174
196
  ### Standalone React (Vite, CRA, etc.)
175
197
 
@@ -28,6 +28,11 @@
28
28
  "title": "Enable overlay scrollbars",
29
29
  "code": "import \"overlayscrollbars/overlayscrollbars.css\";\nimport { OverlayScrollbarProvider } from \"@underverse-ui/underverse\";\n\nexport function App(){\n return <><OverlayScrollbarProvider enabled theme=\"os-theme-underverse\" autoHide=\"leave\" />{/* app */}</>;\n}\n"
30
30
  },
31
+ {
32
+ "id": "overlay-scrollbar-global-selector",
33
+ "title": "Enable global selector mode",
34
+ "code": "import \"overlayscrollbars/overlayscrollbars.css\";\nimport { OverlayScrollbarProvider } from \"@underverse-ui/underverse\";\n\nexport function App(){\n return <><OverlayScrollbarProvider selector=\".overflow-auto, .overflow-y-auto, .overflow-x-auto, [data-os-scrollbar]\" />{/* app */}</>;\n}\n"
35
+ },
31
36
  {
32
37
  "id": "api-discovery-order",
33
38
  "title": "How agents should discover API",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 207,
6
6
  "exports": [
package/dist/index.cjs CHANGED
@@ -4007,7 +4007,6 @@ var Popover = ({
4007
4007
  "div",
4008
4008
  {
4009
4009
  ...contentProps,
4010
- "data-os-scrollbar": contentScrollable ? true : void 0,
4011
4010
  className: cn(
4012
4011
  "rounded-2xl md:rounded-3xl border bg-popover text-popover-foreground shadow-md",
4013
4012
  "backdrop-blur-sm bg-popover/95 border-border/60 p-4",
@@ -4227,7 +4226,6 @@ var Sheet = ({
4227
4226
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4228
4227
  "div",
4229
4228
  {
4230
- "data-os-scrollbar": true,
4231
4229
  className: "flex-1 overflow-auto p-4",
4232
4230
  style: {
4233
4231
  opacity: open && !isAnimating ? 1 : 0,
@@ -4691,7 +4689,7 @@ var Tabs = ({
4691
4689
  );
4692
4690
  const activeTab = tabs.find((tab) => tab.value === active);
4693
4691
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: cn("w-full", orientation === "vertical" && "flex gap-6"), children: [
4694
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: containerClasses, role: "tablist", "aria-orientation": orientation, "data-os-scrollbar": orientation === "horizontal" ? true : void 0, children: [
4692
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: containerClasses, role: "tablist", "aria-orientation": orientation, children: [
4695
4693
  tabs.map((tab, index) => {
4696
4694
  const isActive = active === tab.value;
4697
4695
  const Icon = tab.icon;
@@ -5241,7 +5239,7 @@ var Combobox = ({
5241
5239
  }
5242
5240
  )
5243
5241
  ] }) }),
5244
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { "data-os-scrollbar": true, className: "overflow-y-auto overscroll-contain", style: { maxHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: cn(size === "sm" ? "p-1" : size === "lg" ? "p-2" : "p-1.5"), children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
5242
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "overflow-y-auto overscroll-contain", style: { maxHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: cn(size === "sm" ? "p-1" : size === "lg" ? "p-2" : "p-1.5"), children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
5245
5243
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "relative", children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "w-10 h-10 rounded-full border-2 border-primary/20 border-t-primary animate-spin" }) }),
5246
5244
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { className: "text-sm text-muted-foreground", children: loadingText })
5247
5245
  ] }) }) : filteredOptions.length > 0 ? groupedOptions ? (
@@ -5834,7 +5832,7 @@ var ScrollArea = (0, import_react14.forwardRef)(
5834
5832
  className
5835
5833
  ),
5836
5834
  ...props,
5837
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: cn("h-full w-full overflow-y-auto scroll-area-viewport custom-scrollbar", contentClassName), "data-os-scrollbar": true, children })
5835
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: cn("h-full w-full overflow-y-auto scroll-area-viewport custom-scrollbar", contentClassName), children })
5838
5836
  }
5839
5837
  );
5840
5838
  }
@@ -6297,7 +6295,6 @@ var DatePicker = ({
6297
6295
  sizeStyles8[size].contentPadding,
6298
6296
  "animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-300"
6299
6297
  ),
6300
- contentScrollable: true,
6301
6298
  trigger: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
6302
6299
  "button",
6303
6300
  {
@@ -6653,7 +6650,6 @@ var DateRangePicker = ({ startDate, endDate, onChange, placeholder = "Select dat
6653
6650
  size === "sm" ? "p-3" : "p-5",
6654
6651
  "animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-300"
6655
6652
  ),
6656
- contentScrollable: true,
6657
6653
  trigger: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
6658
6654
  "button",
6659
6655
  {
@@ -7104,7 +7100,6 @@ function WheelColumn({
7104
7100
  "div",
7105
7101
  {
7106
7102
  ref: scrollRef,
7107
- "data-os-scrollbar": true,
7108
7103
  className: cn(
7109
7104
  "h-full overflow-y-auto overscroll-contain snap-y snap-mandatory",
7110
7105
  "select-none cursor-grab active:cursor-grabbing",
@@ -8528,7 +8523,6 @@ function WheelColumn2({
8528
8523
  "div",
8529
8524
  {
8530
8525
  ref: scrollRef,
8531
- "data-os-scrollbar": true,
8532
8526
  className: cn(
8533
8527
  "h-full overflow-y-auto overscroll-contain snap-y snap-mandatory",
8534
8528
  "select-none cursor-grab active:cursor-grabbing",
@@ -9476,7 +9470,6 @@ var DateTimePicker = ({
9476
9470
  // Keep the popover usable on small viewports
9477
9471
  "max-w-[calc(100vw-1rem)] max-h-[calc(100vh-6rem)] overflow-auto"
9478
9472
  ),
9479
- contentScrollable: true,
9480
9473
  placement: "bottom-end",
9481
9474
  children: [
9482
9475
  /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-border", children: [
@@ -10148,7 +10141,7 @@ function CalendarTimelineHeader(props) {
10148
10141
  contentClassName: cn(
10149
10142
  "w-auto p-0 rounded-2xl md:rounded-3xl overflow-hidden"
10150
10143
  ),
10151
- children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { "data-os-scrollbar": true, className: "max-w-[calc(100vw-1rem)] max-h-[calc(100vh-6rem)] overflow-auto", children: [
10144
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "max-w-[calc(100vw-1rem)] max-h-[calc(100vh-6rem)] overflow-auto", children: [
10152
10145
  /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-border", children: [
10153
10146
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "p-2", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
10154
10147
  Calendar3,
@@ -10292,7 +10285,7 @@ function CalendarTimelineHeader(props) {
10292
10285
  ]
10293
10286
  }
10294
10287
  ) : null,
10295
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { ref: headerRef, "data-os-scrollbar": true, className: "flex-1 min-w-0 overflow-x-auto overflow-y-hidden", children: slotHeaderNodes })
10288
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { ref: headerRef, className: "flex-1 min-w-0 overflow-x-auto overflow-y-hidden", children: slotHeaderNodes })
10296
10289
  ] })
10297
10290
  ] });
10298
10291
  }
@@ -11883,7 +11876,6 @@ function CalendarTimeline({
11883
11876
  "div",
11884
11877
  {
11885
11878
  ref: leftRef,
11886
- "data-os-scrollbar": true,
11887
11879
  className: "shrink-0 overflow-y-auto overflow-x-hidden",
11888
11880
  style: { width: effectiveResourceColumnWidth, minWidth: effectiveResourceColumnWidth },
11889
11881
  children: [
@@ -11915,7 +11907,6 @@ function CalendarTimeline({
11915
11907
  {
11916
11908
  ref: bodyRef,
11917
11909
  className: "relative flex-1 overflow-auto custom-scrollbar",
11918
- "data-os-scrollbar": true,
11919
11910
  onPointerMove,
11920
11911
  onPointerUp,
11921
11912
  onPointerLeave: () => setHoverCell(null),
@@ -12509,7 +12500,6 @@ var MultiCombobox = ({
12509
12500
  id: listboxId,
12510
12501
  role: "listbox",
12511
12502
  "aria-multiselectable": "true",
12512
- "data-os-scrollbar": true,
12513
12503
  style: { maxHeight },
12514
12504
  className: cn("overflow-y-auto p-1.5", size === "lg" ? "text-base" : size === "sm" ? "text-xs" : "text-sm"),
12515
12505
  children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("li", { className: "px-3 py-8 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
@@ -14135,7 +14125,6 @@ function CategoryTreeSelect(props) {
14135
14125
  "p-2",
14136
14126
  "animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-300"
14137
14127
  ),
14138
- "data-os-scrollbar": true,
14139
14128
  children: [
14140
14129
  renderSearch(),
14141
14130
  renderTreeContent()
@@ -15146,7 +15135,6 @@ function Carousel({
15146
15135
  showThumbnails && totalSlides > slidesToShow && /* @__PURE__ */ (0, import_jsx_runtime47.jsx)(
15147
15136
  "div",
15148
15137
  {
15149
- "data-os-scrollbar": true,
15150
15138
  className: cn(
15151
15139
  "absolute bottom-0 left-0 right-0 flex gap-2 p-4 bg-linear-to-t from-black/50 to-transparent overflow-x-auto",
15152
15140
  isHorizontal ? "flex-row" : "flex-col"
@@ -16038,7 +16026,6 @@ var TimelineRoot = React43.forwardRef(
16038
16026
  "div",
16039
16027
  {
16040
16028
  ref,
16041
- "data-os-scrollbar": mode === "horizontal" ? true : void 0,
16042
16029
  className: cn("relative", mode === "horizontal" && "flex gap-4 overflow-x-auto", mode === "vertical" && "space-y-0", className),
16043
16030
  ...rest,
16044
16031
  children: mode === "vertical" ? /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("div", { className: "space-y-0", children: content }) : content
@@ -17034,7 +17021,7 @@ var MusicPlayer = ({
17034
17021
  }
17035
17022
  ) })
17036
17023
  ] }),
17037
- showPlaylist && /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("div", { "data-os-scrollbar": true, className: "bg-muted/50 backdrop-blur-sm max-h-96 overflow-y-auto border-t border-border", children: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "p-4", children: [
17024
+ showPlaylist && /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("div", { className: "bg-muted/50 backdrop-blur-sm max-h-96 overflow-y-auto border-t border-border", children: /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("div", { className: "p-4", children: [
17038
17025
  /* @__PURE__ */ (0, import_jsx_runtime53.jsxs)("h3", { className: "text-lg font-semibold text-foreground mb-3", children: [
17039
17026
  "Playlist (",
17040
17027
  playlist.length,
@@ -19033,21 +19020,127 @@ var LoadingBar = ({
19033
19020
  // ../../components/ui/OverlayScrollbarProvider.tsx
19034
19021
  var import_react33 = require("react");
19035
19022
  var import_overlayscrollbars = require("overlayscrollbars");
19036
- var SCROLLABLE_SELECTOR = "[data-os-scrollbar]";
19037
- var PORTAL_EXCLUDE_SELECTOR = [
19023
+
19024
+ // ../../components/ui/overlay-scrollbar-controller.ts
19025
+ var DEFAULT_OVERLAY_SCROLLBAR_SELECTOR = [
19026
+ ".overflow-auto",
19027
+ ".overflow-y-auto",
19028
+ ".overflow-x-auto",
19029
+ ".overflow-scroll",
19030
+ ".overflow-y-scroll",
19031
+ ".overflow-x-scroll",
19032
+ "textarea",
19033
+ "[data-os-scrollbar]"
19034
+ ].join(", ");
19035
+ var DEFAULT_OVERLAY_SCROLLBAR_EXCLUDE = [
19036
+ "html",
19037
+ "body",
19038
+ "[data-os-ignore]",
19038
19039
  "[data-radix-portal]",
19039
19040
  "[role='dialog']",
19040
19041
  "[aria-modal='true']",
19041
19042
  "[data-sonner-toaster]"
19042
19043
  ].join(", ");
19043
- function shouldSkip(element) {
19044
- if (element === document.body || element === document.documentElement) return true;
19044
+ function splitSelectorList(selectorList) {
19045
+ return selectorList.split(",").map((part) => part.trim()).filter(Boolean);
19046
+ }
19047
+ function safeMatches(element, selector) {
19048
+ try {
19049
+ return element.matches(selector);
19050
+ } catch {
19051
+ return false;
19052
+ }
19053
+ }
19054
+ function safeClosest(element, selector) {
19055
+ try {
19056
+ return element.closest(selector);
19057
+ } catch {
19058
+ return null;
19059
+ }
19060
+ }
19061
+ function shouldSkipElement(element, excludeSelectors, ancestorExcludeSelectors) {
19062
+ const tagName = element.tagName?.toLowerCase?.() ?? "";
19063
+ if (tagName === "body" || tagName === "html") return true;
19045
19064
  if (element.classList.contains("scrollbar-none")) return true;
19046
- if (element.hasAttribute("data-os-ignore")) return true;
19047
19065
  if (element.hasAttribute("data-overlayscrollbars")) return true;
19048
- if (element.closest(PORTAL_EXCLUDE_SELECTOR)) return true;
19066
+ if (excludeSelectors.some((selector) => safeMatches(element, selector))) return true;
19067
+ if (ancestorExcludeSelectors.some((selector) => safeClosest(element, selector))) return true;
19049
19068
  return false;
19050
19069
  }
19070
+ function createOverlayScrollbarController({
19071
+ selector,
19072
+ exclude,
19073
+ options,
19074
+ createInstance,
19075
+ root = document.body,
19076
+ createObserver,
19077
+ requestAnimationFrameImpl = requestAnimationFrame,
19078
+ cancelAnimationFrameImpl = cancelAnimationFrame
19079
+ }) {
19080
+ const instances = /* @__PURE__ */ new Map();
19081
+ const excludeSelectors = splitSelectorList(exclude);
19082
+ const ancestorExcludeSelectors = excludeSelectors.filter((item) => item !== "html" && item !== "body");
19083
+ let rafId = 0;
19084
+ const init = (element) => {
19085
+ if (shouldSkipElement(element, excludeSelectors, ancestorExcludeSelectors)) return;
19086
+ if (instances.has(element)) return;
19087
+ instances.set(element, createInstance(element, options));
19088
+ };
19089
+ const scan = (root2) => {
19090
+ if (root2 instanceof HTMLElement && safeMatches(root2, selector)) {
19091
+ init(root2);
19092
+ }
19093
+ if (!("querySelectorAll" in root2)) return;
19094
+ try {
19095
+ root2.querySelectorAll(selector).forEach(init);
19096
+ } catch {
19097
+ }
19098
+ };
19099
+ const cleanup = () => {
19100
+ instances.forEach((instance, element) => {
19101
+ if (!element.isConnected) {
19102
+ instance.destroy();
19103
+ instances.delete(element);
19104
+ }
19105
+ });
19106
+ };
19107
+ scan(root);
19108
+ const onMutations = (mutations) => {
19109
+ if (rafId) return;
19110
+ rafId = requestAnimationFrameImpl(() => {
19111
+ rafId = 0;
19112
+ const roots = /* @__PURE__ */ new Set();
19113
+ mutations.forEach((mutation) => {
19114
+ if (mutation.target && (typeof mutation.target.querySelectorAll === "function" || mutation.target instanceof HTMLElement)) {
19115
+ roots.add(mutation.target);
19116
+ }
19117
+ Array.from(mutation.addedNodes).forEach((node) => {
19118
+ if (node instanceof HTMLElement) {
19119
+ roots.add(node);
19120
+ }
19121
+ });
19122
+ });
19123
+ roots.forEach(scan);
19124
+ cleanup();
19125
+ });
19126
+ };
19127
+ const observer = createObserver ? createObserver(onMutations) : new MutationObserver(onMutations);
19128
+ observer.observe(root, { childList: true, subtree: true, attributes: true });
19129
+ const destroy = () => {
19130
+ if (rafId) cancelAnimationFrameImpl(rafId);
19131
+ observer.disconnect();
19132
+ instances.forEach((instance) => instance.destroy());
19133
+ instances.clear();
19134
+ };
19135
+ return {
19136
+ destroy,
19137
+ scan,
19138
+ cleanup,
19139
+ getInstanceCount: () => instances.size
19140
+ };
19141
+ }
19142
+
19143
+ // ../../components/ui/OverlayScrollbarProvider.tsx
19051
19144
  function OverlayScrollbarProvider({
19052
19145
  enabled = true,
19053
19146
  theme = "os-theme-underverse",
@@ -19055,7 +19148,9 @@ function OverlayScrollbarProvider({
19055
19148
  autoHide = "leave",
19056
19149
  autoHideDelay = 600,
19057
19150
  dragScroll = true,
19058
- clickScroll = false
19151
+ clickScroll = false,
19152
+ selector = DEFAULT_OVERLAY_SCROLLBAR_SELECTOR,
19153
+ exclude = DEFAULT_OVERLAY_SCROLLBAR_EXCLUDE
19059
19154
  } = {}) {
19060
19155
  (0, import_react33.useEffect)(() => {
19061
19156
  if (typeof window === "undefined") return;
@@ -19070,56 +19165,16 @@ function OverlayScrollbarProvider({
19070
19165
  clickScroll
19071
19166
  }
19072
19167
  };
19073
- const instances = /* @__PURE__ */ new Map();
19074
- let rafId = 0;
19075
- const init = (element) => {
19076
- if (shouldSkip(element)) return;
19077
- if (instances.has(element)) return;
19078
- instances.set(element, (0, import_overlayscrollbars.OverlayScrollbars)(element, options));
19079
- };
19080
- const scan = (root) => {
19081
- if (root instanceof HTMLElement && root.matches(SCROLLABLE_SELECTOR)) {
19082
- init(root);
19083
- }
19084
- if (!("querySelectorAll" in root)) return;
19085
- root.querySelectorAll(SCROLLABLE_SELECTOR).forEach(init);
19086
- };
19087
- const cleanup = () => {
19088
- instances.forEach((instance, element) => {
19089
- if (!element.isConnected) {
19090
- instance.destroy();
19091
- instances.delete(element);
19092
- }
19093
- });
19094
- };
19095
- scan(document.body);
19096
- const observer = new MutationObserver((mutations) => {
19097
- if (rafId) return;
19098
- rafId = requestAnimationFrame(() => {
19099
- rafId = 0;
19100
- const scanRoots = /* @__PURE__ */ new Set();
19101
- mutations.forEach((mutation) => {
19102
- if (mutation.target instanceof HTMLElement || mutation.target instanceof Document || mutation.target instanceof DocumentFragment) {
19103
- scanRoots.add(mutation.target);
19104
- }
19105
- mutation.addedNodes.forEach((node) => {
19106
- if (node instanceof HTMLElement) {
19107
- scanRoots.add(node);
19108
- }
19109
- });
19110
- });
19111
- scanRoots.forEach(scan);
19112
- cleanup();
19113
- });
19168
+ const controller = createOverlayScrollbarController({
19169
+ selector,
19170
+ exclude,
19171
+ options,
19172
+ createInstance: (element, instanceOptions) => (0, import_overlayscrollbars.OverlayScrollbars)(element, instanceOptions)
19114
19173
  });
19115
- observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] });
19116
19174
  return () => {
19117
- if (rafId) cancelAnimationFrame(rafId);
19118
- observer.disconnect();
19119
- instances.forEach((instance) => instance.destroy());
19120
- instances.clear();
19175
+ controller.destroy();
19121
19176
  };
19122
- }, [enabled, theme, visibility, autoHide, autoHideDelay, dragScroll, clickScroll]);
19177
+ }, [enabled, theme, visibility, autoHide, autoHideDelay, dragScroll, clickScroll, selector, exclude]);
19123
19178
  return null;
19124
19179
  }
19125
19180
  var OverlayScrollbarProvider_default = OverlayScrollbarProvider;
@@ -19141,7 +19196,6 @@ var Table = import_react34.default.forwardRef(({ className, containerClassName,
19141
19196
  "backdrop-blur-sm transition-all duration-300",
19142
19197
  containerClassName
19143
19198
  ),
19144
- "data-os-scrollbar": true,
19145
19199
  children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)("table", { ref, className: cn("w-full caption-bottom text-sm", className), ...props })
19146
19200
  }
19147
19201
  );
@@ -20073,13 +20127,8 @@ function DataTable({
20073
20127
  children: /* @__PURE__ */ (0, import_jsx_runtime68.jsx)(
20074
20128
  "div",
20075
20129
  {
20076
- className: "custom-scrollbar w-full",
20077
- "data-os-scrollbar": true,
20078
- style: stickyHeader ? {
20079
- maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
20080
- overflowY: "auto",
20081
- overflowX: "auto"
20082
- } : { overflowX: "auto" },
20130
+ className: cn("w-full overflow-x-auto", stickyHeader && "overflow-y-auto"),
20131
+ style: stickyHeader ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
20083
20132
  children: /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(
20084
20133
  Table,
20085
20134
  {
@@ -21672,7 +21721,7 @@ var CommandList = (0, import_react42.forwardRef)((props, ref) => {
21672
21721
  if (props.items.length === 0) {
21673
21722
  return /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("div", { className: "w-72 p-4 text-center text-sm text-muted-foreground", children: "No results" });
21674
21723
  }
21675
- return /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("div", { ref: listRef, "data-os-scrollbar": true, className: "w-72 max-h-80 overflow-y-auto bg-card border border-border rounded-2xl shadow-lg", children: [
21724
+ return /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)("div", { ref: listRef, className: "w-72 max-h-80 overflow-y-auto bg-card border border-border rounded-2xl shadow-lg", children: [
21676
21725
  /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("div", { className: "px-3 py-2 border-b", children: /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("span", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: "Basic Blocks" }) }),
21677
21726
  /* @__PURE__ */ (0, import_jsx_runtime77.jsx)("div", { className: "p-1", children: props.items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime77.jsxs)(
21678
21727
  "button",
@@ -22765,7 +22814,7 @@ var EmojiList = (0, import_react44.forwardRef)((props, ref) => {
22765
22814
  if (props.items.length === 0) {
22766
22815
  return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "w-80 p-4 text-center text-sm text-muted-foreground bg-card border border-border rounded-2xl shadow-lg", children: "No emoji found" });
22767
22816
  }
22768
- return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { "data-os-scrollbar": true, className: "w-80 max-h-80 overflow-y-auto bg-card border border-border rounded-2xl shadow-lg", children: [
22817
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "w-80 max-h-80 overflow-y-auto bg-card border border-border rounded-2xl shadow-lg", children: [
22769
22818
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "px-3 py-2 border-b bg-muted/30", children: /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "flex items-center gap-2", children: [
22770
22819
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(import_lucide_react41.Smile, { className: "w-4 h-4 text-primary" }),
22771
22820
  /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: "Emoji" })
@@ -23134,7 +23183,6 @@ function buildUEditorExtensions({
23134
23183
  import_extension_code_block_lowlight.default.configure({
23135
23184
  lowlight,
23136
23185
  HTMLAttributes: {
23137
- "data-os-scrollbar": "true",
23138
23186
  class: "rounded-lg bg-[#1e1e1e] p-4 font-mono text-sm overflow-x-auto"
23139
23187
  }
23140
23188
  }),
@@ -23465,7 +23513,6 @@ var EmojiPicker = ({ onSelect, onClose }) => {
23465
23513
  "div",
23466
23514
  {
23467
23515
  ref: scrollContainerRef,
23468
- "data-os-scrollbar": true,
23469
23516
  className: "overflow-y-auto px-3 py-2 shrink",
23470
23517
  style: { height: "20rem" },
23471
23518
  children: search ? (
@@ -24165,7 +24212,7 @@ var SlashCommandMenu = ({ editor, onClose, filterText = "" }) => {
24165
24212
  if (commands.length === 0) {
24166
24213
  return /* @__PURE__ */ (0, import_jsx_runtime84.jsx)("div", { className: "w-72 p-4 text-center text-muted-foreground text-sm", children: t("slashCommand.noResults") });
24167
24214
  }
24168
- return /* @__PURE__ */ (0, import_jsx_runtime84.jsxs)("div", { ref: menuRef, "data-os-scrollbar": true, className: "w-72 max-h-80 overflow-y-auto", children: [
24215
+ return /* @__PURE__ */ (0, import_jsx_runtime84.jsxs)("div", { ref: menuRef, className: "w-72 max-h-80 overflow-y-auto", children: [
24169
24216
  /* @__PURE__ */ (0, import_jsx_runtime84.jsx)("div", { className: "px-3 py-2 border-b", children: /* @__PURE__ */ (0, import_jsx_runtime84.jsx)("span", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: t("slashCommand.basicBlocks") }) }),
24170
24217
  /* @__PURE__ */ (0, import_jsx_runtime84.jsx)("div", { className: "p-1", children: commands.map((cmd, index) => /* @__PURE__ */ (0, import_jsx_runtime84.jsxs)(
24171
24218
  "button",
@@ -24927,7 +24974,6 @@ var UEditor = import_react52.default.forwardRef(({
24927
24974
  import_react53.EditorContent,
24928
24975
  {
24929
24976
  editor,
24930
- "data-os-scrollbar": true,
24931
24977
  className: "flex-1 overflow-y-auto",
24932
24978
  style: {
24933
24979
  minHeight,