@zovaris/sephiro 0.9.0 → 0.10.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/dist/index.js CHANGED
@@ -133,16 +133,32 @@ function usePortaled() {
133
133
  }, []);
134
134
  return portaled;
135
135
  }
136
+ function anchorArea(side, align) {
137
+ if (side === "left" || side === "right") return `${side} span-all`;
138
+ return `${side} ${align === "center" ? "span-all" : align === "start" ? "span-right" : "span-left"}`;
139
+ }
136
140
  /**
137
- * Measures trigger and content, then pins the overlay to fixed coordinates.
138
- * Flips, clamps, and re-measures on scroll (capture, so inner scrollers
139
- * count) and resize while active.
141
+ * Uses native CSS anchors when supported; otherwise measures fixed coordinates.
142
+ * Native anchors follow scrolling without JS; older browsers re-measure on
143
+ * scroll (including inner scrollers). Both paths react to size changes.
140
144
  */
141
145
  function useOverlayPosition(triggerRef, contentRef, active, placement = {}) {
142
146
  const { side = "bottom", align = "start", offset = 4, padding = 8, matchWidth = false } = placement;
143
147
  const frameRef = useRef(null);
148
+ const anchorName = `--sph-overlay-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
144
149
  useIsoLayoutEffect(() => {
145
150
  if (!active) return;
151
+ const trigger = triggerRef.current;
152
+ const content = contentRef.current;
153
+ if (!trigger || !content) return;
154
+ inheritMotionScope(trigger, content);
155
+ const native = typeof CSS !== "undefined" && CSS.supports("anchor-name", anchorName) && CSS.supports("position-area", "bottom span-right") && CSS.supports("position-try-fallbacks", "flip-block");
156
+ const previousAnchor = trigger.style.getPropertyValue("anchor-name");
157
+ if (native) {
158
+ trigger.style.setProperty("anchor-name", previousAnchor && previousAnchor !== "none" ? `${previousAnchor}, ${anchorName}` : anchorName);
159
+ content.style.setProperty("position-anchor", anchorName);
160
+ content.style.setProperty("position-visibility", "anchors-visible");
161
+ }
146
162
  const update = () => {
147
163
  frameRef.current = null;
148
164
  const trigger = triggerRef.current;
@@ -164,32 +180,65 @@ function useOverlayPosition(triggerRef, contentRef, active, placement = {}) {
164
180
  padding
165
181
  });
166
182
  const style = content.style;
167
- style.left = `${resolved.left}px`;
168
- style.top = `${resolved.top}px`;
169
- style.right = "auto";
170
- style.bottom = "auto";
171
- style.translate = "none";
183
+ if (native) {
184
+ style.inset = "auto";
185
+ style.translate = "none";
186
+ style.setProperty("position-area", anchorArea(resolved.side, align));
187
+ style.setProperty("position-try-fallbacks", side === "left" || side === "right" ? "flip-inline, flip-block, flip-inline flip-block" : "flip-block, flip-inline, flip-block flip-inline");
188
+ style.margin = `${padding}px`;
189
+ if (side === "top" || side === "bottom") {
190
+ if (align === "start") style.marginLeft = "0";
191
+ if (align === "end") style.marginRight = "0";
192
+ }
193
+ style.setProperty(`margin-${resolved.side === "bottom" ? "top" : resolved.side === "top" ? "bottom" : resolved.side === "right" ? "left" : "right"}`, `${offset}px`);
194
+ style.justifySelf = side === "left" || side === "right" ? "normal" : align === "center" ? "anchor-center" : align;
195
+ style.alignSelf = side === "left" || side === "right" ? "anchor-center" : "normal";
196
+ } else {
197
+ style.left = `${resolved.left}px`;
198
+ style.top = `${resolved.top}px`;
199
+ style.right = "auto";
200
+ style.bottom = "auto";
201
+ style.translate = "none";
202
+ }
172
203
  style.transformOrigin = overlayOrigin(resolved.side, align);
173
204
  content.dataset.side = resolved.side;
174
205
  };
175
206
  const schedule = () => {
176
207
  if (frameRef.current === null) frameRef.current = requestAnimationFrame(update);
177
208
  };
178
- inheritMotionScope(triggerRef.current, contentRef.current);
179
209
  update();
210
+ const observer = new ResizeObserver(schedule);
211
+ observer.observe(trigger);
212
+ observer.observe(content);
180
213
  window.addEventListener("resize", schedule);
181
- document.addEventListener("scroll", schedule, {
214
+ if (!native) document.addEventListener("scroll", schedule, {
182
215
  capture: true,
183
216
  passive: true
184
217
  });
185
218
  return () => {
219
+ observer.disconnect();
186
220
  window.removeEventListener("resize", schedule);
187
221
  document.removeEventListener("scroll", schedule, { capture: true });
188
222
  if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);
223
+ frameRef.current = null;
224
+ if (native) {
225
+ if (previousAnchor) trigger.style.setProperty("anchor-name", previousAnchor);
226
+ else trigger.style.removeProperty("anchor-name");
227
+ for (const name of [
228
+ "position-anchor",
229
+ "position-area",
230
+ "position-try-fallbacks",
231
+ "position-visibility",
232
+ "margin",
233
+ "justify-self",
234
+ "align-self"
235
+ ]) content.style.removeProperty(name);
236
+ }
189
237
  };
190
238
  }, [
191
239
  active,
192
240
  align,
241
+ anchorName,
193
242
  matchWidth,
194
243
  offset,
195
244
  padding,
@@ -343,13 +392,23 @@ function Avatar({ src, name, size = "md", status, className, ...props }) {
343
392
  }
344
393
  //#endregion
345
394
  //#region src/atoms/Badge/index.tsx
346
- function Badge({ variant = "neutral", size = "sm", className, children, ...props }) {
347
- return /* @__PURE__ */ jsx("span", {
395
+ function Badge({ variant = "neutral", size = "sm", onRemove, removeLabel, className, children, ...props }) {
396
+ return /* @__PURE__ */ jsxs("span", {
348
397
  ...node(props),
349
398
  className: cn("sph-badge", className),
350
399
  "data-variant": variant,
351
400
  "data-size": size,
352
- children: node(children)
401
+ "data-removable": onRemove ? true : void 0,
402
+ children: [node(children), onRemove ? /* @__PURE__ */ jsx("button", {
403
+ type: "button",
404
+ className: "sph-badge__remove",
405
+ "aria-label": removeLabel ?? (typeof children === "string" ? `Remove ${children}` : "Remove"),
406
+ onClick: onRemove,
407
+ children: /* @__PURE__ */ jsx(XIcon, {
408
+ size: 10,
409
+ "aria-hidden": "true"
410
+ })
411
+ }) : null]
353
412
  });
354
413
  }
355
414
  //#endregion
@@ -466,10 +525,11 @@ function Input({ invalid = false, size = "md", className, "aria-invalid": ariaIn
466
525
  }
467
526
  //#endregion
468
527
  //#region src/atoms/Kbd/index.tsx
469
- function Kbd({ keys, className }) {
528
+ function Kbd({ keys, variant = "solid", className }) {
470
529
  const caps = Array.isArray(keys) ? keys : [keys];
471
530
  return /* @__PURE__ */ jsx("kbd", {
472
531
  className: cn("sph-kbd", className),
532
+ "data-variant": variant,
473
533
  children: caps.map((cap, index) => /* @__PURE__ */ jsx("kbd", {
474
534
  className: "sph-kbd__key",
475
535
  children: node(cap)
@@ -677,14 +737,14 @@ function Accordion({ items, type = "single", value, defaultValue, onValueChange,
677
737
  });
678
738
  }
679
739
  //#endregion
680
- //#region src/molecules/Alert/index.tsx
681
- const alertIcons = {
682
- info: /* @__PURE__ */ jsx(InfoIcon, { size: 16 }),
683
- success: /* @__PURE__ */ jsx(CheckIcon, { size: 16 }),
684
- warning: /* @__PURE__ */ jsx(WarningIcon, { size: 16 }),
685
- danger: /* @__PURE__ */ jsx(XIcon, { size: 16 })
740
+ //#region src/molecules/Notice/index.tsx
741
+ const noticeIcons = {
742
+ info: /* @__PURE__ */ jsx(InfoIcon, { size: 14 }),
743
+ success: /* @__PURE__ */ jsx(CheckIcon, { size: 14 }),
744
+ warning: /* @__PURE__ */ jsx(WarningIcon, { size: 14 }),
745
+ danger: /* @__PURE__ */ jsx(XIcon, { size: 14 })
686
746
  };
687
- function Alert({ variant = "info", title, children, dismissible = false, onDismiss, className, role, ...props }) {
747
+ function Notice({ variant = "info", title, children, dismissible = false, onDismiss, className, role, ...props }) {
688
748
  const rootRef = useRef(null);
689
749
  const timerRef = useRef(void 0);
690
750
  const [dismissing, setDismissing] = useState(false);
@@ -708,30 +768,30 @@ function Alert({ variant = "info", title, children, dismissible = false, onDismi
708
768
  return /* @__PURE__ */ jsxs("div", {
709
769
  ref: rootRef,
710
770
  ...node(props),
711
- className: cn("sph-alert", className),
771
+ className: cn("sph-notice", className),
712
772
  "data-variant": variant,
713
773
  "data-state": dismissing ? "closed" : "open",
714
774
  role: role ?? (variant === "danger" ? "alert" : "status"),
715
775
  "aria-live": role ? void 0 : variant === "danger" ? "assertive" : "polite",
716
776
  children: [
717
777
  /* @__PURE__ */ jsx("span", {
718
- className: "sph-alert__icon",
778
+ className: "sph-notice__icon",
719
779
  "aria-hidden": "true",
720
- children: alertIcons[variant]
780
+ children: noticeIcons[variant]
721
781
  }),
722
782
  /* @__PURE__ */ jsxs("div", {
723
- className: "sph-alert__body",
783
+ className: "sph-notice__body",
724
784
  children: [title !== void 0 && /* @__PURE__ */ jsx("p", {
725
- className: "sph-alert__title",
785
+ className: "sph-notice__title",
726
786
  children: node(title)
727
787
  }), children !== void 0 && /* @__PURE__ */ jsx("div", {
728
- className: "sph-alert__content",
788
+ className: "sph-notice__content",
729
789
  children: node(children)
730
790
  })]
731
791
  }),
732
792
  dismissible && /* @__PURE__ */ jsx("button", {
733
793
  type: "button",
734
- className: "sph-alert__dismiss",
794
+ className: "sph-notice__dismiss",
735
795
  "aria-label": "Dismiss notification",
736
796
  onClick: requestDismiss,
737
797
  children: /* @__PURE__ */ jsx(XIcon, {
@@ -742,7 +802,6 @@ function Alert({ variant = "info", title, children, dismissible = false, onDismi
742
802
  ]
743
803
  });
744
804
  }
745
- const Notice = Alert;
746
805
  //#endregion
747
806
  //#region src/molecules/Attachment/index.tsx
748
807
  const IMAGE_EXT = [
@@ -1146,6 +1205,7 @@ function Calendar({ value, onValueChange, month, onMonthChange, min, max, weekSt
1146
1205
  return /* @__PURE__ */ jsxs("button", {
1147
1206
  type: "button",
1148
1207
  role: "gridcell",
1208
+ className: "sph-calendar__day",
1149
1209
  "data-date": day,
1150
1210
  "data-today": day === today || void 0,
1151
1211
  "data-in-range": inSpan || void 0,
@@ -1833,8 +1893,20 @@ function ContextMenu({ children, items, onSelect, disabled = false, label = "Con
1833
1893
  const target = event.target;
1834
1894
  if (!rootRef.current?.contains(target) && !contentRef.current?.contains(target)) close();
1835
1895
  };
1896
+ const handleScroll = (event) => {
1897
+ if (!contentRef.current?.contains(event.target)) close();
1898
+ };
1836
1899
  document.addEventListener("pointerdown", handlePointerDown);
1837
- return () => document.removeEventListener("pointerdown", handlePointerDown);
1900
+ document.addEventListener("scroll", handleScroll, {
1901
+ capture: true,
1902
+ passive: true
1903
+ });
1904
+ window.addEventListener("resize", close);
1905
+ return () => {
1906
+ document.removeEventListener("pointerdown", handlePointerDown);
1907
+ document.removeEventListener("scroll", handleScroll, { capture: true });
1908
+ window.removeEventListener("resize", close);
1909
+ };
1838
1910
  }, [open]);
1839
1911
  useEffect(() => {
1840
1912
  if (!open) return;
@@ -1847,7 +1919,12 @@ function ContextMenu({ children, items, onSelect, disabled = false, label = "Con
1847
1919
  const el = contentRef.current;
1848
1920
  if (!present || !portaled || !el) return;
1849
1921
  inheritScope(rootRef.current, el);
1850
- const rect = el.getBoundingClientRect();
1922
+ const rect = {
1923
+ right: coords.x + el.offsetWidth,
1924
+ bottom: coords.y + el.offsetHeight,
1925
+ width: el.offsetWidth,
1926
+ height: el.offsetHeight
1927
+ };
1851
1928
  const margin = 8;
1852
1929
  const flipX = rect.right > window.innerWidth - margin;
1853
1930
  const flipY = rect.bottom > window.innerHeight - margin;
@@ -1855,7 +1932,11 @@ function ContextMenu({ children, items, onSelect, disabled = false, label = "Con
1855
1932
  if (flipY) el.style.top = `${Math.max(margin, coords.y - rect.height)}px`;
1856
1933
  el.style.transformOrigin = `${flipX ? "right" : "left"} ${flipY ? "bottom" : "top"}`;
1857
1934
  el.dataset.side = flipY ? "top" : "bottom";
1858
- }, [present, coords]);
1935
+ }, [
1936
+ present,
1937
+ portaled,
1938
+ coords
1939
+ ]);
1859
1940
  return /* @__PURE__ */ jsxs("div", {
1860
1941
  ...node(props),
1861
1942
  ref: rootRef,
@@ -1954,7 +2035,7 @@ const modals = createLayerStack();
1954
2035
  * themselves (clear a query, pop a level) before closing.
1955
2036
  */
1956
2037
  function useFocusTrap(ref, active, initial, options = {}) {
1957
- const { fallbackToContainer = false } = options;
2038
+ const { fallbackToContainer = false, trap = true } = options;
1958
2039
  const initialRef = useRef(initial);
1959
2040
  useEffect(() => {
1960
2041
  initialRef.current = initial;
@@ -1969,10 +2050,10 @@ function useFocusTrap(ref, active, initial, options = {}) {
1969
2050
  const resolve = initialRef.current;
1970
2051
  const explicit = typeof resolve === "function" ? resolve() : resolve?.current;
1971
2052
  if (!explicit && container.contains(document.activeElement)) return;
1972
- (explicit ?? focusable(container)[0] ?? (fallbackToContainer ? container : void 0))?.focus();
2053
+ (explicit ?? focusable(container)[0] ?? (fallbackToContainer ? container : void 0))?.focus({ preventScroll: true });
1973
2054
  });
1974
2055
  const handleKeyDown = (event) => {
1975
- if (event.key !== "Tab" || traps.top() !== layer) return;
2056
+ if (!trap || event.key !== "Tab" || traps.top() !== layer) return;
1976
2057
  const container = ref.current;
1977
2058
  const focused = document.activeElement;
1978
2059
  const lost = !focused || focused === document.body;
@@ -1990,7 +2071,8 @@ function useFocusTrap(ref, active, initial, options = {}) {
1990
2071
  }, [
1991
2072
  active,
1992
2073
  fallbackToContainer,
1993
- ref
2074
+ ref,
2075
+ trap
1994
2076
  ]);
1995
2077
  }
1996
2078
  function canReceiveFocus(element) {
@@ -2091,11 +2173,13 @@ function inertTargets(keep, root, owned = /* @__PURE__ */ new Set()) {
2091
2173
  }
2092
2174
  const inerted = /* @__PURE__ */ new Set();
2093
2175
  function syncInert() {
2094
- const top = modals.top()?.element;
2176
+ const layer = modals.top();
2177
+ const top = layer?.element;
2178
+ const root = layer?.root ?? document.body;
2095
2179
  const next = /* @__PURE__ */ new Set();
2096
- if (top && document.body.contains(top)) {
2097
- const regions = Array.from(document.body.querySelectorAll(LIVE_REGION_SELECTOR)).filter((region) => !region.contains(top) && !top.contains(region));
2098
- for (const element of inertTargets([top, ...regions], document.body, inerted)) next.add(element);
2180
+ if (top && root.contains(top)) {
2181
+ const regions = Array.from(root.querySelectorAll(LIVE_REGION_SELECTOR)).filter((region) => !region.contains(top) && !top.contains(region));
2182
+ for (const element of inertTargets([top, ...regions], root, inerted)) next.add(element);
2099
2183
  }
2100
2184
  for (const element of inerted) if (!next.has(element)) {
2101
2185
  element.removeAttribute("inert");
@@ -2106,13 +2190,16 @@ function syncInert() {
2106
2190
  inerted.add(element);
2107
2191
  }
2108
2192
  }
2109
- function useModalLayer(ref, active) {
2193
+ function useModalLayer(ref, active, scope = "page") {
2110
2194
  const layerRef = useRef(null);
2111
2195
  const handle = useRef(null);
2112
2196
  handle.current ??= { isTopmost: () => layerRef.current !== null && modals.top() === layerRef.current };
2113
2197
  useEffect(() => {
2114
2198
  if (!active) return;
2115
- const layer = { element: ref.current };
2199
+ const layer = {
2200
+ element: ref.current,
2201
+ root: scope === "local" ? ref.current?.closest("[data-sph-modal-scope]") ?? ref.current?.parentElement?.parentElement ?? void 0 : void 0
2202
+ };
2116
2203
  layerRef.current = layer;
2117
2204
  const release = modals.push(layer);
2118
2205
  syncInert();
@@ -2121,24 +2208,40 @@ function useModalLayer(ref, active) {
2121
2208
  if (layerRef.current === layer) layerRef.current = null;
2122
2209
  syncInert();
2123
2210
  };
2124
- }, [active, ref]);
2211
+ }, [
2212
+ active,
2213
+ ref,
2214
+ scope
2215
+ ]);
2125
2216
  return handle.current;
2126
2217
  }
2127
2218
  //#endregion
2128
2219
  //#region src/molecules/Dialog/index.tsx
2129
- function Dialog({ open, onClose, title, description, children, footer, closeLabel = "Close dialog", closeOnOverlayClick = true, closeOnEscape = true, initialFocus, returnFocus = true, className }) {
2220
+ function Dialog({ open, onClose, title, description, children, footer, closeLabel = "Close dialog", closeOnOverlayClick = true, closeOnEscape = true, initialFocus, returnFocus = true, scope = "page", className }) {
2130
2221
  const id = useId();
2131
2222
  const titleId = `${id}-title`;
2132
2223
  const descriptionId = description !== void 0 ? `${id}-description` : void 0;
2133
2224
  const { present, state, ref: dialogRef } = usePresence(open);
2225
+ const backdropRef = useRef(null);
2226
+ const portaled = usePortaled();
2227
+ useIsoLayoutEffect(() => {
2228
+ if (scope === "page" && present && portaled) inheritScope(document.activeElement, backdropRef.current);
2229
+ }, [
2230
+ present,
2231
+ portaled,
2232
+ scope
2233
+ ]);
2134
2234
  const active = open && present;
2135
- const layer = useModalLayer(dialogRef, active);
2136
- useFocusTrap(dialogRef, active, initialFocus, { fallbackToContainer: true });
2235
+ const layer = useModalLayer(dialogRef, active, scope);
2236
+ useFocusTrap(dialogRef, active, initialFocus, {
2237
+ fallbackToContainer: true,
2238
+ trap: scope === "page"
2239
+ });
2137
2240
  useReturnFocus(present, {
2138
2241
  ref: dialogRef,
2139
2242
  returnFocus
2140
2243
  });
2141
- useScrollLock(present);
2244
+ useScrollLock(present && scope === "page");
2142
2245
  useEffect(() => {
2143
2246
  if (!open || !closeOnEscape) return;
2144
2247
  const handleKeyDown = (event) => {
@@ -2156,7 +2259,9 @@ function Dialog({ open, onClose, title, description, children, footer, closeLabe
2156
2259
  open
2157
2260
  ]);
2158
2261
  if (!present) return null;
2159
- return /* @__PURE__ */ jsx("div", {
2262
+ const content = /* @__PURE__ */ jsx("div", {
2263
+ ref: backdropRef,
2264
+ "data-scope": scope,
2160
2265
  className: "sph-dialog__backdrop",
2161
2266
  "data-state": state,
2162
2267
  role: "presentation",
@@ -2167,7 +2272,7 @@ function Dialog({ open, onClose, title, description, children, footer, closeLabe
2167
2272
  ref: dialogRef,
2168
2273
  className: cn("sph-dialog", className),
2169
2274
  role: "dialog",
2170
- "aria-modal": "true",
2275
+ "aria-modal": scope === "page" ? true : void 0,
2171
2276
  tabIndex: -1,
2172
2277
  "aria-labelledby": titleId,
2173
2278
  "aria-describedby": descriptionId,
@@ -2207,22 +2312,35 @@ function Dialog({ open, onClose, title, description, children, footer, closeLabe
2207
2312
  ]
2208
2313
  })
2209
2314
  });
2315
+ return scope === "page" && portaled ? createPortal(content, document.body) : content;
2210
2316
  }
2211
2317
  //#endregion
2212
2318
  //#region src/molecules/Drawer/index.tsx
2213
- function Drawer({ open, onClose, title, description, children, footer, side = "right", size = "md", closeLabel = "Close panel", closeOnOverlayClick = true, closeOnEscape = true, initialFocus, returnFocus = true, className }) {
2319
+ function Drawer({ open, onClose, title, description, children, footer, side = "right", size = "md", closeLabel = "Close panel", closeOnOverlayClick = true, closeOnEscape = true, initialFocus, returnFocus = true, scope = "page", className }) {
2214
2320
  const id = useId();
2215
2321
  const titleId = `${id}-title`;
2216
2322
  const descriptionId = description !== void 0 ? `${id}-description` : void 0;
2217
2323
  const { present, state, ref: drawerRef } = usePresence(open);
2324
+ const backdropRef = useRef(null);
2325
+ const portaled = usePortaled();
2326
+ useIsoLayoutEffect(() => {
2327
+ if (scope === "page" && present && portaled) inheritScope(document.activeElement, backdropRef.current);
2328
+ }, [
2329
+ present,
2330
+ portaled,
2331
+ scope
2332
+ ]);
2218
2333
  const active = open && present;
2219
- const layer = useModalLayer(drawerRef, active);
2220
- useFocusTrap(drawerRef, active, initialFocus, { fallbackToContainer: true });
2334
+ const layer = useModalLayer(drawerRef, active, scope);
2335
+ useFocusTrap(drawerRef, active, initialFocus, {
2336
+ fallbackToContainer: true,
2337
+ trap: scope === "page"
2338
+ });
2221
2339
  useReturnFocus(present, {
2222
2340
  ref: drawerRef,
2223
2341
  returnFocus
2224
2342
  });
2225
- useScrollLock(present);
2343
+ useScrollLock(present && scope === "page");
2226
2344
  useEffect(() => {
2227
2345
  if (!open || !closeOnEscape) return;
2228
2346
  const handleKeyDown = (event) => {
@@ -2240,7 +2358,9 @@ function Drawer({ open, onClose, title, description, children, footer, side = "r
2240
2358
  open
2241
2359
  ]);
2242
2360
  if (!present) return null;
2243
- return /* @__PURE__ */ jsx("div", {
2361
+ const content = /* @__PURE__ */ jsx("div", {
2362
+ ref: backdropRef,
2363
+ "data-scope": scope,
2244
2364
  className: "sph-drawer__backdrop",
2245
2365
  "data-state": state,
2246
2366
  "data-side": side,
@@ -2254,7 +2374,7 @@ function Drawer({ open, onClose, title, description, children, footer, side = "r
2254
2374
  "data-side": side,
2255
2375
  "data-size": size,
2256
2376
  role: "dialog",
2257
- "aria-modal": "true",
2377
+ "aria-modal": scope === "page" ? true : void 0,
2258
2378
  tabIndex: -1,
2259
2379
  "aria-labelledby": titleId,
2260
2380
  "aria-describedby": descriptionId,
@@ -2294,6 +2414,7 @@ function Drawer({ open, onClose, title, description, children, footer, side = "r
2294
2414
  ]
2295
2415
  })
2296
2416
  });
2417
+ return scope === "page" && portaled ? createPortal(content, document.body) : content;
2297
2418
  }
2298
2419
  //#endregion
2299
2420
  //#region src/molecules/FieldMessage/index.tsx
@@ -3469,6 +3590,8 @@ function firstEnabled(items) {
3469
3590
  }
3470
3591
  function Tabs({ items, value, defaultValue, onValueChange, children, ariaLabel = "Tabs", orientation = "horizontal", className }) {
3471
3592
  const generatedId = useId();
3593
+ const listRef = useRef(null);
3594
+ const [measured, setMeasured] = useState(false);
3472
3595
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? firstEnabled(items) ?? "");
3473
3596
  const activeValue = value ?? uncontrolledValue;
3474
3597
  const activeIndex = Math.max(0, items.findIndex((item) => item.value === activeValue));
@@ -3496,15 +3619,44 @@ function Tabs({ items, value, defaultValue, onValueChange, children, ariaLabel =
3496
3619
  }
3497
3620
  };
3498
3621
  const panelId = `${generatedId}-panel`;
3622
+ useIsoLayoutEffect(() => {
3623
+ const list = listRef.current;
3624
+ if (!list) return;
3625
+ const update = () => {
3626
+ const tab = list.querySelectorAll("[role=\"tab\"]")[activeIndex];
3627
+ if (!tab) return;
3628
+ const listRect = list.getBoundingClientRect();
3629
+ const tabRect = tab.getBoundingClientRect();
3630
+ const vertical = orientation === "vertical";
3631
+ list.style.setProperty("--sph-tabs-indicator-at", `${(vertical ? tabRect.top - listRect.top : tabRect.left - listRect.left).toFixed(2)}px`);
3632
+ list.style.setProperty("--sph-tabs-indicator-size", `${(vertical ? tabRect.height : tabRect.width).toFixed(2)}px`);
3633
+ setMeasured(true);
3634
+ };
3635
+ update();
3636
+ if (typeof ResizeObserver === "undefined") {
3637
+ window.addEventListener("resize", update);
3638
+ return () => window.removeEventListener("resize", update);
3639
+ }
3640
+ const observer = new ResizeObserver(update);
3641
+ observer.observe(list);
3642
+ for (const tab of list.querySelectorAll("[role='tab']")) observer.observe(tab);
3643
+ return () => observer.disconnect();
3644
+ }, [
3645
+ activeIndex,
3646
+ items,
3647
+ orientation
3648
+ ]);
3499
3649
  return /* @__PURE__ */ jsxs("div", {
3500
3650
  className: cn("sph-tabs", className),
3501
3651
  "data-orientation": orientation,
3502
- children: [/* @__PURE__ */ jsx("div", {
3652
+ children: [/* @__PURE__ */ jsxs("div", {
3653
+ ref: listRef,
3503
3654
  className: "sph-tabs__list",
3504
3655
  role: "tablist",
3505
3656
  "aria-label": ariaLabel,
3506
3657
  "aria-orientation": orientation,
3507
- children: items.map((item, index) => {
3658
+ "data-measured": measured || void 0,
3659
+ children: [items.map((item, index) => {
3508
3660
  const selected = item.value === activeValue;
3509
3661
  return /* @__PURE__ */ jsx("button", {
3510
3662
  id: `${generatedId}-${index}`,
@@ -3520,7 +3672,10 @@ function Tabs({ items, value, defaultValue, onValueChange, children, ariaLabel =
3520
3672
  onKeyDown: (event) => move(event, index),
3521
3673
  children: node(item.label)
3522
3674
  }, item.value);
3523
- })
3675
+ }), /* @__PURE__ */ jsx("span", {
3676
+ className: "sph-tabs__indicator",
3677
+ "aria-hidden": "true"
3678
+ })]
3524
3679
  }), activeItem && (activeItem.content !== void 0 || children !== void 0) && /* @__PURE__ */ jsx("div", {
3525
3680
  id: panelId,
3526
3681
  className: "sph-tabs__panel",
@@ -4419,7 +4574,13 @@ function CommandPalette({ open, onOpenChange, items, onSelect, placeholder = "Ty
4419
4574
  ]);
4420
4575
  useEffect(() => {
4421
4576
  if (!visible || active < 0) return;
4422
- (trackRef.current?.querySelector("[data-active=\"true\"]"))?.scrollIntoView({ block: "nearest" });
4577
+ const option = trackRef.current?.querySelector("[data-active=\"true\"]");
4578
+ const list = innerRef.current;
4579
+ if (!option || !list) return;
4580
+ const top = option.offsetTop;
4581
+ const bottom = top + option.offsetHeight;
4582
+ if (top < list.scrollTop) list.scrollTop = top;
4583
+ else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;
4423
4584
  }, [active, visible]);
4424
4585
  useIsoLayoutEffect(() => {
4425
4586
  const indicator = indicatorRef.current;
@@ -4427,14 +4588,12 @@ function CommandPalette({ open, onOpenChange, items, onSelect, placeholder = "Ty
4427
4588
  if (!visible || !indicator || !track || active < 0) return;
4428
4589
  const option = track.querySelector(`[data-index="${active}"]`);
4429
4590
  if (!option) return;
4430
- const trackRect = track.getBoundingClientRect();
4431
- const optionRect = option.getBoundingClientRect();
4432
4591
  const first = !indicator.style.width;
4433
4592
  if (first) indicator.style.transition = "none";
4434
4593
  indicator.style.opacity = "1";
4435
- indicator.style.width = `${optionRect.width}px`;
4436
- indicator.style.height = `${optionRect.height}px`;
4437
- indicator.style.transform = `translate(${optionRect.left - trackRect.left}px, ${optionRect.top - trackRect.top}px)`;
4594
+ indicator.style.width = `${option.offsetWidth}px`;
4595
+ indicator.style.height = `${option.offsetHeight}px`;
4596
+ indicator.style.transform = `translate(${option.offsetLeft}px, ${option.offsetTop}px)`;
4438
4597
  if (first) requestAnimationFrame(() => {
4439
4598
  indicator.style.transition = "";
4440
4599
  });
@@ -4456,12 +4615,12 @@ function CommandPalette({ open, onOpenChange, items, onSelect, placeholder = "Ty
4456
4615
  body.style.height = "";
4457
4616
  return;
4458
4617
  }
4459
- const current = body.style.height ? Number.parseFloat(body.style.height) : body.getBoundingClientRect().height;
4618
+ const current = body.style.height ? Number.parseFloat(body.style.height) : body.offsetHeight;
4460
4619
  if (!body.style.height) {
4461
4620
  body.style.height = `${current}px`;
4462
4621
  return;
4463
4622
  }
4464
- const target = inner.getBoundingClientRect().height;
4623
+ const target = inner.offsetHeight;
4465
4624
  if (Math.abs(current - target) < 1) {
4466
4625
  body.style.height = `${target}px`;
4467
4626
  return;
@@ -6464,7 +6623,20 @@ function fitOverflowCount({ widths, available, gap, reserve, minVisible = 0 }) {
6464
6623
  return visible >= total ? total : visible;
6465
6624
  }
6466
6625
  const defaultLabel = (count) => `${count} more`;
6467
- function OverflowPanel({ hidden, offset, render, getKey, onEscape }) {
6626
+ const defaultRemoveLabel = (item) => `Remove ${String(item)}`;
6627
+ function OverflowRemove({ label, onClick }) {
6628
+ return /* @__PURE__ */ jsx("button", {
6629
+ type: "button",
6630
+ className: "sph-overflow-list__remove",
6631
+ "aria-label": label,
6632
+ onClick,
6633
+ children: /* @__PURE__ */ jsx(XIcon, {
6634
+ size: 13,
6635
+ "aria-hidden": "true"
6636
+ })
6637
+ });
6638
+ }
6639
+ function OverflowPanel({ hidden, offset, render, getKey, remove, onEscape }) {
6468
6640
  const panelRef = useRef(null);
6469
6641
  useFocusTrap(panelRef, true);
6470
6642
  useEffect(() => {
@@ -6482,13 +6654,13 @@ function OverflowPanel({ hidden, offset, render, getKey, onEscape }) {
6482
6654
  className: "sph-overflow-list__panel",
6483
6655
  tabIndex: -1,
6484
6656
  onKeyDown: handleKeyDown,
6485
- children: hidden.map((item, position) => /* @__PURE__ */ jsx("li", {
6657
+ children: hidden.map((item, position) => /* @__PURE__ */ jsxs("li", {
6486
6658
  className: "sph-overflow-list__panel-item",
6487
- children: node(render(item, offset + position))
6659
+ children: [node(render(item, offset + position)), remove ? node(remove(item, offset + position)) : null]
6488
6660
  }, getKey(item, offset + position)))
6489
6661
  });
6490
6662
  }
6491
- function OverflowList({ items, renderItem, renderOverflow, renderOverflowItem, getKey = (_item, index) => index, minVisible = 0, collapseFrom = "end", overflowLabel = defaultLabel, className, ...props }) {
6663
+ function OverflowList({ items, renderItem, renderOverflow, renderOverflowItem, getKey = (_item, index) => index, minVisible = 0, collapseFrom = "end", overflowLabel = defaultLabel, onRemove, removeLabel = defaultRemoveLabel, className, ...props }) {
6492
6664
  const rootRef = useRef(null);
6493
6665
  const measureRef = useRef(null);
6494
6666
  const trackRef = useRef(null);
@@ -6575,6 +6747,10 @@ function OverflowList({ items, renderItem, renderOverflow, renderOverflowItem, g
6575
6747
  const focusTrigger = () => {
6576
6748
  chipRef.current?.closest("button")?.focus();
6577
6749
  };
6750
+ const removable = onRemove ? (item, index) => /* @__PURE__ */ jsx(OverflowRemove, {
6751
+ label: removeLabel(item, index),
6752
+ onClick: () => onRemove(item, index)
6753
+ }) : void 0;
6578
6754
  const chipContent = (count, ref) => /* @__PURE__ */ jsxs("span", {
6579
6755
  ref,
6580
6756
  className: "sph-overflow-list__chip",
@@ -6601,6 +6777,7 @@ function OverflowList({ items, renderItem, renderOverflow, renderOverflowItem, g
6601
6777
  offset,
6602
6778
  render: renderOverflowItem ?? renderItem,
6603
6779
  getKey,
6780
+ remove: removable,
6604
6781
  onEscape: focusTrigger
6605
6782
  })
6606
6783
  })
@@ -6613,9 +6790,9 @@ function OverflowList({ items, renderItem, renderOverflow, renderOverflowItem, g
6613
6790
  "data-overflowing": hiddenCount > 0 || void 0,
6614
6791
  children: [
6615
6792
  collapseFrom === "start" && more,
6616
- items.slice(start, start + shown).map((item, position) => /* @__PURE__ */ jsx("li", {
6793
+ items.slice(start, start + shown).map((item, position) => /* @__PURE__ */ jsxs("li", {
6617
6794
  className: "sph-overflow-list__item",
6618
- children: node(renderItem(item, start + position))
6795
+ children: [node(renderItem(item, start + position)), removable ? node(removable(item, start + position)) : null]
6619
6796
  }, getKey(item, start + position))),
6620
6797
  collapseFrom === "end" && more,
6621
6798
  ready && /* @__PURE__ */ jsx("li", {
@@ -6625,9 +6802,9 @@ function OverflowList({ items, renderItem, renderOverflow, renderOverflowItem, g
6625
6802
  children: /* @__PURE__ */ jsxs("div", {
6626
6803
  ref: trackRef,
6627
6804
  className: "sph-overflow-list__track",
6628
- children: [items.map((item, index) => /* @__PURE__ */ jsx("div", {
6805
+ children: [items.map((item, index) => /* @__PURE__ */ jsxs("div", {
6629
6806
  className: "sph-overflow-list__item",
6630
- children: node(renderItem(item, index))
6807
+ children: [node(renderItem(item, index)), removable ? node(removable(item, index)) : null]
6631
6808
  }, getKey(item, index))), /* @__PURE__ */ jsx("div", {
6632
6809
  className: "sph-overflow-list__item",
6633
6810
  children: renderOverflow ? node(renderOverflow(items)) : chipContent(total)
@@ -7508,6 +7685,6 @@ function ChatScroller({ children, itemCount, newCount, threshold = 48, ariaLabel
7508
7685
  });
7509
7686
  }
7510
7687
  //#endregion
7511
- export { Accordion, Alert, AnimatedNumber, Attachment, Avatar, AvatarGroup, Badge, Breadcrumb, Button, Calendar, Card, Chart, ChatScroller, Checkbox, Citation, Collapsible, Combobox, CommandPalette, ContextMenu, Delta, Dialog, Drawer, EmptyState, Field, FieldMessage, HoldButton, HoverCard, IconButton, Input, Kbd, MagneticDropzone, Menu, Menubar, MetadataList, Notice, NumberInput, OTPInput, OverflowList, Pagination, PlanCard, Popover, Progress, QuestionCard, RadioGroup, ReasoningSteps, Resizable, Reveal, ScrollArea, SegmentedControl, Select, Separator, SidePanel, Sidebar, Skeleton, Slider, Spinner, Stat, StatGroup, StatusMark, Stepper, StreamingText, Surface, Table, Tabs, TaskList, Textarea, Timeline, Timestamp, Toast, ToastViewport, Toggle, ToolCalls, Toolbar, Tooltip, TooltipGroup, describeDropzoneResult, motionDurationMs, parseDurationMs, toast, usePresence, validateDropzoneFiles };
7688
+ export { Accordion, AnimatedNumber, Attachment, Avatar, AvatarGroup, Badge, Breadcrumb, Button, Calendar, Card, Chart, ChatScroller, Checkbox, Citation, Collapsible, Combobox, CommandPalette, ContextMenu, Delta, Dialog, Drawer, EmptyState, Field, FieldMessage, HoldButton, HoverCard, IconButton, Input, Kbd, MagneticDropzone, Menu, Menubar, MetadataList, Notice, NumberInput, OTPInput, OverflowList, Pagination, PlanCard, Popover, Progress, QuestionCard, RadioGroup, ReasoningSteps, Resizable, Reveal, ScrollArea, SegmentedControl, Select, Separator, SidePanel, Sidebar, Skeleton, Slider, Spinner, Stat, StatGroup, StatusMark, Stepper, StreamingText, Surface, Table, Tabs, TaskList, Textarea, Timeline, Timestamp, Toast, ToastViewport, Toggle, ToolCalls, Toolbar, Tooltip, TooltipGroup, describeDropzoneResult, motionDurationMs, parseDurationMs, toast, usePresence, validateDropzoneFiles };
7512
7689
 
7513
7690
  //# sourceMappingURL=index.js.map