@geomak/ui 7.8.0 → 7.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
@@ -2,7 +2,7 @@ import { X, Error as Error$1, Warning, CheckCircle, Info } from './chunk-KAFJJO5
2
2
  export { icons_default as Icon, createIcon } from './chunk-KAFJJO5O.js';
3
3
  import { colors_default } from './chunk-DXOWXLKK.js';
4
4
  export { colors_default as COLORS, PALETTE as palette, semanticTokens, vars } from './chunk-DXOWXLKK.js';
5
- import React34, { useMemo, createContext, useState, useEffect, useId, useCallback, useRef, useContext, useSyncExternalStore, useLayoutEffect } from 'react';
5
+ import React36, { useMemo, createContext, useState, useEffect, useId, useCallback, useRef, useContext, useSyncExternalStore, useLayoutEffect } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
@@ -453,7 +453,7 @@ function Typography({
453
453
  }
454
454
  );
455
455
  }
456
- var IconButton = React34.forwardRef(function IconButton2({
456
+ var IconButton = React36.forwardRef(function IconButton2({
457
457
  icon,
458
458
  onClick,
459
459
  type = "primary",
@@ -562,7 +562,7 @@ var SIZE_CLASSES = {
562
562
  md: "h-9 px-4 text-sm gap-1.5 rounded-lg",
563
563
  lg: "h-11 px-5 text-sm gap-2 rounded-xl"
564
564
  };
565
- var Button = React34.forwardRef(function Button2({
565
+ var Button = React36.forwardRef(function Button2({
566
566
  content,
567
567
  variant = "primary",
568
568
  size = "md",
@@ -670,7 +670,7 @@ function MenuButton({
670
670
  "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
671
671
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95"
672
672
  ].join(" "),
673
- children: items.map((item) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
673
+ children: items.map((item) => /* @__PURE__ */ jsxs(React36.Fragment, { children: [
674
674
  item.separatorBefore && /* @__PURE__ */ jsx(DropdownMenu2.Separator, { className: "my-1 h-px bg-border" }),
675
675
  /* @__PURE__ */ jsxs(
676
676
  DropdownMenu2.Item,
@@ -693,6 +693,10 @@ function MenuButton({
693
693
  ) })
694
694
  ] });
695
695
  }
696
+ var DialogContainerContext = createContext(null);
697
+ function useDialogContainer() {
698
+ return useContext(DialogContainerContext);
699
+ }
696
700
  var SIZE_MAP = {
697
701
  sm: 400,
698
702
  md: 600,
@@ -717,6 +721,7 @@ function Modal({
717
721
  className = ""
718
722
  }) {
719
723
  const reduced = useReducedMotion();
724
+ const [panelEl, setPanelEl] = useState(null);
720
725
  const maxWidth = width ?? SIZE_MAP[size];
721
726
  return /* @__PURE__ */ jsx(Dialog.Root, { open, onOpenChange: (next) => {
722
727
  if (!next) onClose?.();
@@ -734,6 +739,7 @@ function Modal({
734
739
  /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(Dialog.Content, { asChild: true, children: /* @__PURE__ */ jsxs(
735
740
  motion.div,
736
741
  {
742
+ ref: setPanelEl,
737
743
  className: `fixed left-1/2 top-1/2 z-modal flex flex-col w-[calc(100%-2rem)] max-h-[90dvh] bg-surface rounded-2xl shadow-xl overflow-hidden focus:outline-none ${className}`.trim(),
738
744
  style: {
739
745
  maxWidth,
@@ -761,7 +767,7 @@ function Modal({
761
767
  }
762
768
  ) })
763
769
  ] }),
764
- /* @__PURE__ */ jsx("div", { className: `flex-1 overflow-y-auto p-5 ${hasFooter ? "" : "pb-5"}`, children }),
770
+ /* @__PURE__ */ jsx("div", { className: `flex-1 overflow-y-auto p-5 ${hasFooter ? "" : "pb-5"}`, children: /* @__PURE__ */ jsx(DialogContainerContext.Provider, { value: panelEl, children }) }),
765
771
  hasFooter && /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center justify-end gap-3 border-t border-border px-5 py-3", children: [
766
772
  /* @__PURE__ */ jsx(
767
773
  Button_default,
@@ -812,6 +818,7 @@ function Drawer({
812
818
  className = ""
813
819
  }) {
814
820
  const reduced = useReducedMotion();
821
+ const [panelEl, setPanelEl] = useState(null);
815
822
  const isRight = placement === "right";
816
823
  const hiddenX = isRight ? "100%" : "-100%";
817
824
  const resolvedWidth = width ?? SIZE_MAP2[size];
@@ -832,6 +839,7 @@ function Drawer({
832
839
  /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsx(Dialog.Content, { asChild: true, children: /* @__PURE__ */ jsxs(
833
840
  motion.div,
834
841
  {
842
+ ref: setPanelEl,
835
843
  className: `fixed top-0 bottom-0 ${isRight ? "right-0" : "left-0"} z-modal flex flex-col bg-surface shadow-xl focus:outline-none ${className}`.trim(),
836
844
  style: { width: `min(calc(100vw - 1rem), ${widthCss})` },
837
845
  initial: { x: reduced ? 0 : hiddenX, opacity: reduced ? 0 : 1 },
@@ -858,7 +866,7 @@ function Drawer({
858
866
  }
859
867
  ) })
860
868
  ] }),
861
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-5", children }),
869
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-5", children: /* @__PURE__ */ jsx(DialogContainerContext.Provider, { value: panelEl, children }) }),
862
870
  hasFooter && /* @__PURE__ */ jsxs("div", { className: `flex flex-shrink-0 items-center gap-3 border-t border-border px-5 py-3 ${isRight ? "justify-start" : "justify-end"}`, children: [
863
871
  /* @__PURE__ */ jsx(Button_default, { style: { width: 90 }, variant: cancelVariant, content: cancelText, onClick: onCancel }),
864
872
  /* @__PURE__ */ jsx(Button_default, { style: { width: 90 }, variant: okVariant, content: okText, onClick: onOk })
@@ -1817,7 +1825,7 @@ function Kbd({
1817
1825
  style
1818
1826
  }) {
1819
1827
  if (keys && keys.length > 0) {
1820
- return /* @__PURE__ */ jsx("span", { className: cx("inline-flex items-center gap-1", className), style, children: keys.map((k, i) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
1828
+ return /* @__PURE__ */ jsx("span", { className: cx("inline-flex items-center gap-1", className), style, children: keys.map((k, i) => /* @__PURE__ */ jsxs(React36.Fragment, { children: [
1821
1829
  i > 0 && /* @__PURE__ */ jsx("span", { className: "text-foreground-muted text-xs select-none", children: separator }),
1822
1830
  /* @__PURE__ */ jsx("kbd", { className: [cap, SIZE3[size]].join(" "), children: k })
1823
1831
  ] }, `${k}-${i}`)) });
@@ -1909,7 +1917,7 @@ function FlatCarousel({
1909
1917
  style
1910
1918
  }) {
1911
1919
  const scrollerRef = useRef(null);
1912
- const slides = React34.Children.toArray(children);
1920
+ const slides = React36.Children.toArray(children);
1913
1921
  const [active, setActive] = useState(0);
1914
1922
  const [atStart, setAtStart] = useState(true);
1915
1923
  const [atEnd, setAtEnd] = useState(false);
@@ -1964,7 +1972,7 @@ function RotatingCarousel({
1964
1972
  className = "",
1965
1973
  style
1966
1974
  }) {
1967
- const slides = React34.Children.toArray(children);
1975
+ const slides = React36.Children.toArray(children);
1968
1976
  const count = slides.length;
1969
1977
  const [active, setActive] = useState(0);
1970
1978
  const reduced = useReducedMotion();
@@ -5245,7 +5253,7 @@ function Wizard({
5245
5253
  ] });
5246
5254
  }
5247
5255
  var SearchIcon = /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "w-4 h-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M10.5 3.75a6.75 6.75 0 100 13.5 6.75 6.75 0 000-13.5zM2.25 10.5a8.25 8.25 0 1114.59 5.28l4.69 4.69a.75.75 0 11-1.06 1.06l-4.69-4.69A8.25 8.25 0 012.25 10.5z", clipRule: "evenodd" }) });
5248
- var SearchInput = React34.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5256
+ var SearchInput = React36.forwardRef(function SearchInput2({ value, onChange, disabled, label, htmlFor, placeholder, name, inputStyle, style, layout = "vertical", size = "md", icon, helperText, className }, ref) {
5249
5257
  return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, layout, helperText, children: /* @__PURE__ */ jsxs(
5250
5258
  "div",
5251
5259
  {
@@ -5562,7 +5570,7 @@ function TableBody({
5562
5570
  return /* @__PURE__ */ jsx("tbody", { children: rows.map((row, i) => {
5563
5571
  const rowKey = getRowKey(row, i);
5564
5572
  const isExpanded = expanded.has(rowKey);
5565
- return /* @__PURE__ */ jsxs(React34.Fragment, { children: [
5573
+ return /* @__PURE__ */ jsxs(React36.Fragment, { children: [
5566
5574
  /* @__PURE__ */ jsxs(
5567
5575
  "tr",
5568
5576
  {
@@ -5937,6 +5945,15 @@ async function sourceToBytes(source, remote) {
5937
5945
  }
5938
5946
  throw new Error("Unsupported source type");
5939
5947
  }
5948
+ function downloadBlob(blob, name) {
5949
+ if (typeof document === "undefined") return;
5950
+ const url = URL.createObjectURL(blob);
5951
+ const a = document.createElement("a");
5952
+ a.href = url;
5953
+ a.download = name;
5954
+ a.click();
5955
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
5956
+ }
5940
5957
  function sourceName(source) {
5941
5958
  if (typeof File !== "undefined" && source instanceof File) return source.name;
5942
5959
  if (isUrlSource(source)) {
@@ -5947,17 +5964,45 @@ function sourceName(source) {
5947
5964
  return void 0;
5948
5965
  }
5949
5966
  var pdfjsPromise = null;
5950
- function loadPdfjs() {
5967
+ function loadPdfjs(workerSrc) {
5951
5968
  if (pdfjsPromise) return pdfjsPromise;
5952
5969
  pdfjsPromise = import('pdfjs-dist').then((pdfjs) => {
5953
5970
  if (!pdfjs.GlobalWorkerOptions.workerSrc) {
5954
- pdfjs.GlobalWorkerOptions.workerSrc = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
5971
+ pdfjs.GlobalWorkerOptions.workerSrc = workerSrc ?? `https://cdn.jsdelivr.net/npm/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
5955
5972
  }
5956
5973
  return pdfjs;
5957
5974
  });
5958
5975
  return pdfjsPromise;
5959
5976
  }
5960
5977
  var GAP = 12;
5978
+ var SEARCH_DEBOUNCE = 220;
5979
+ var TEXT_LAYER_STYLE_ID = "oxygen-pdf-textlayer";
5980
+ var TEXT_LAYER_CSS = `
5981
+ .oxy-textLayer{position:absolute;inset:0;overflow:clip;line-height:1;text-size-adjust:none;forced-color-adjust:none;transform-origin:0 0}
5982
+ .oxy-textLayer span,.oxy-textLayer br{color:transparent;position:absolute;white-space:pre;cursor:text;transform-origin:0 0}
5983
+ .oxy-textLayer span.markedContent{top:0;height:0}
5984
+ .oxy-textLayer ::selection{background:color-mix(in srgb, var(--color-accent) 35%, transparent)}
5985
+ `;
5986
+ function ensureTextLayerStyles() {
5987
+ if (typeof document === "undefined" || document.getElementById(TEXT_LAYER_STYLE_ID)) return;
5988
+ const el = document.createElement("style");
5989
+ el.id = TEXT_LAYER_STYLE_ID;
5990
+ el.textContent = TEXT_LAYER_CSS;
5991
+ document.head.appendChild(el);
5992
+ }
5993
+ function pageIndexAt(offsets, y) {
5994
+ let lo = 0;
5995
+ let hi = offsets.length - 1;
5996
+ let ans = 0;
5997
+ while (lo <= hi) {
5998
+ const mid = lo + hi >> 1;
5999
+ if (offsets[mid] <= y) {
6000
+ ans = mid;
6001
+ lo = mid + 1;
6002
+ } else hi = mid - 1;
6003
+ }
6004
+ return ans;
6005
+ }
5961
6006
  function PdfViewer({
5962
6007
  source,
5963
6008
  remote,
@@ -5966,9 +6011,12 @@ function PdfViewer({
5966
6011
  toolbar = true,
5967
6012
  thumbnails = false,
5968
6013
  textLayer = true,
6014
+ workerSrc,
5969
6015
  onLoad,
5970
6016
  onError,
5971
6017
  onPageChange,
6018
+ height = 600,
6019
+ width,
5972
6020
  className = "",
5973
6021
  style
5974
6022
  }) {
@@ -5976,6 +6024,7 @@ function PdfViewer({
5976
6024
  const [doc, setDoc] = useState(null);
5977
6025
  const [numPages, setNumPages] = useState(0);
5978
6026
  const [baseSize, setBaseSize] = useState(null);
6027
+ const [sizes, setSizes] = useState([]);
5979
6028
  const [status, setStatus] = useState("loading");
5980
6029
  const [error, setError] = useState(null);
5981
6030
  const [reloadKey, setReloadKey] = useState(0);
@@ -5988,6 +6037,13 @@ function PdfViewer({
5988
6037
  const [query, setQuery] = useState("");
5989
6038
  const [matchPages, setMatchPages] = useState(null);
5990
6039
  const [matchIdx, setMatchIdx] = useState(0);
6040
+ const [searching, setSearching] = useState(false);
6041
+ const [pageDraft, setPageDraft] = useState("");
6042
+ const pageEditing = useRef(false);
6043
+ const pageText = useRef(/* @__PURE__ */ new Map());
6044
+ const searchSeq = useRef(0);
6045
+ const searchTimer = useRef(null);
6046
+ const measured = useRef(/* @__PURE__ */ new Set());
5991
6047
  const tb = toolbar === true ? { zoom: true, pager: true, download: true, print: true, search: true } : toolbar || {};
5992
6048
  useEffect(() => {
5993
6049
  let cancelled = false;
@@ -5996,7 +6052,12 @@ function PdfViewer({
5996
6052
  setError(null);
5997
6053
  setDoc(null);
5998
6054
  setBaseSize(null);
5999
- loadPdfjs().then(async (pdfjs2) => {
6055
+ setSizes([]);
6056
+ pageText.current.clear();
6057
+ measured.current = /* @__PURE__ */ new Set();
6058
+ setMatchPages(null);
6059
+ setMatchIdx(0);
6060
+ loadPdfjs(workerSrc).then(async (pdfjs2) => {
6000
6061
  if (cancelled) return;
6001
6062
  setPdfjs(pdfjs2);
6002
6063
  const params = isUrlSource(source) ? { url: urlHref(source), httpHeaders: remote?.httpHeaders, withCredentials: remote?.withCredentials } : { data: source instanceof Uint8Array || source instanceof ArrayBuffer ? source : new Uint8Array(await source.arrayBuffer()) };
@@ -6009,9 +6070,12 @@ function PdfViewer({
6009
6070
  const first = await pdf.getPage(1);
6010
6071
  const vp = first.getViewport({ scale: 1 });
6011
6072
  if (cancelled) return;
6073
+ const base = { width: vp.width, height: vp.height };
6012
6074
  setDoc(pdf);
6013
6075
  setNumPages(pdf.numPages);
6014
- setBaseSize({ width: vp.width, height: vp.height });
6076
+ setBaseSize(base);
6077
+ setSizes(Array.from({ length: pdf.numPages }, () => base));
6078
+ measured.current.add(1);
6015
6079
  setStatus("ready");
6016
6080
  onLoad?.({ numPages: pdf.numPages });
6017
6081
  }).catch((err) => {
@@ -6024,7 +6088,7 @@ function PdfViewer({
6024
6088
  cancelled = true;
6025
6089
  task?.destroy?.();
6026
6090
  };
6027
- }, [source, remote, reloadKey]);
6091
+ }, [source, remote, reloadKey, workerSrc]);
6028
6092
  useEffect(() => () => {
6029
6093
  doc?.destroy?.();
6030
6094
  }, [doc]);
@@ -6037,59 +6101,101 @@ function PdfViewer({
6037
6101
  ro.observe(el);
6038
6102
  return () => ro.disconnect();
6039
6103
  }, [status]);
6104
+ const handleMeasure = useCallback((p, size) => {
6105
+ setSizes((prev) => {
6106
+ const cur = prev[p - 1];
6107
+ if (cur && Math.abs(cur.width - size.width) < 0.5 && Math.abs(cur.height - size.height) < 0.5) return prev;
6108
+ const next = prev.slice();
6109
+ next[p - 1] = size;
6110
+ return next;
6111
+ });
6112
+ }, []);
6040
6113
  const scale = useMemo(() => {
6041
6114
  if (!baseSize) return 1;
6042
6115
  if (typeof zoomMode === "number") return zoomMode;
6043
- const avail = Math.max(0, (viewport.w || baseSize.width) - 32);
6116
+ const avail = Math.max(1, (viewport.w || baseSize.width) - 32);
6044
6117
  if (zoomMode === "page-width" || zoomMode === "auto") return avail / baseSize.width;
6045
6118
  if (zoomMode === "page-fit") {
6046
- const availH = Math.max(0, (viewport.h || baseSize.height) - 32);
6119
+ const availH = Math.max(1, (viewport.h || baseSize.height) - 32);
6047
6120
  return Math.min(avail / baseSize.width, availH / baseSize.height);
6048
6121
  }
6049
6122
  return 1;
6050
6123
  }, [zoomMode, baseSize, viewport]);
6051
- const pageH = baseSize ? baseSize.height * scale + GAP : 0;
6052
- const pageW = baseSize ? baseSize.width * scale : 0;
6053
- const total = numPages * pageH;
6124
+ const { offsets, total } = useMemo(() => {
6125
+ const offs = new Array(sizes.length);
6126
+ let acc = 0;
6127
+ for (let i = 0; i < sizes.length; i++) {
6128
+ offs[i] = acc;
6129
+ acc += sizes[i].height * scale + GAP;
6130
+ }
6131
+ return { offsets: offs, total: acc };
6132
+ }, [sizes, scale]);
6054
6133
  const overscan = 1;
6055
- const startIdx = pageH ? Math.max(0, Math.floor(scrollTop / pageH) - overscan) : 0;
6056
- const endIdx = pageH ? Math.min(numPages, Math.ceil((scrollTop + viewport.h) / pageH) + overscan) : 0;
6134
+ const hasLayout = offsets.length > 0 && total > 0;
6135
+ const startIdx = hasLayout ? Math.max(0, pageIndexAt(offsets, scrollTop) - overscan) : 0;
6136
+ const endIdx = hasLayout ? Math.min(numPages, pageIndexAt(offsets, scrollTop + viewport.h) + 1 + overscan) : 0;
6057
6137
  const visiblePages = Array.from({ length: Math.max(0, endIdx - startIdx) }, (_, i) => startIdx + i + 1);
6138
+ const scrollToPage = useCallback((p) => {
6139
+ const el = scrollRef.current;
6140
+ if (!el || !offsets.length) return;
6141
+ const clamped = Math.min(offsets.length, Math.max(1, p));
6142
+ el.scrollTo({ top: offsets[clamped - 1] ?? 0, behavior: "smooth" });
6143
+ }, [offsets]);
6058
6144
  useEffect(() => {
6059
- if (!pageH) return;
6060
- const cur = Math.min(numPages, Math.max(1, Math.floor((scrollTop + viewport.h / 2) / pageH) + 1));
6145
+ if (!hasLayout) return;
6146
+ const cur = pageIndexAt(offsets, scrollTop + viewport.h / 2) + 1;
6061
6147
  if (cur !== page) {
6062
6148
  setPage(cur);
6063
6149
  onPageChange?.(cur);
6064
6150
  }
6065
- }, [scrollTop, pageH, viewport.h, numPages]);
6066
- const scrollToPage = useCallback((p) => {
6067
- const el = scrollRef.current;
6068
- if (!el || !pageH) return;
6069
- el.scrollTo({ top: (p - 1) * pageH, behavior: "smooth" });
6070
- }, [pageH]);
6151
+ }, [scrollTop, offsets, viewport.h, numPages]);
6071
6152
  useEffect(() => {
6072
6153
  if (status === "ready" && initialPage > 1) scrollToPage(initialPage);
6073
- }, [status]);
6154
+ }, [status, initialPage]);
6074
6155
  const runSearch = useCallback(async (q) => {
6075
- setQuery(q);
6076
- if (!doc || !q.trim()) {
6156
+ const needle = q.trim().toLowerCase();
6157
+ if (!doc || !needle) {
6077
6158
  setMatchPages(null);
6078
6159
  setMatchIdx(0);
6160
+ setSearching(false);
6079
6161
  return;
6080
6162
  }
6081
- const needle = q.toLowerCase();
6163
+ const seq = ++searchSeq.current;
6164
+ setSearching(true);
6082
6165
  const hits = [];
6083
6166
  for (let p = 1; p <= numPages; p++) {
6084
- const pg = await doc.getPage(p);
6085
- const tc = await pg.getTextContent();
6086
- const text = tc.items.map((it) => "str" in it ? it.str : "").join(" ").toLowerCase();
6167
+ let text = pageText.current.get(p);
6168
+ if (text === void 0) {
6169
+ const pg = await doc.getPage(p);
6170
+ const tc = await pg.getTextContent();
6171
+ const built = tc.items.map((it) => "str" in it ? it.str : "").join(" ").toLowerCase();
6172
+ pageText.current.set(p, built);
6173
+ text = built;
6174
+ }
6175
+ if (seq !== searchSeq.current) return;
6087
6176
  if (text.includes(needle)) hits.push(p);
6088
6177
  }
6178
+ if (seq !== searchSeq.current) return;
6089
6179
  setMatchPages(hits);
6090
6180
  setMatchIdx(0);
6181
+ setSearching(false);
6091
6182
  if (hits.length) scrollToPage(hits[0]);
6092
6183
  }, [doc, numPages, scrollToPage]);
6184
+ const onQueryChange = useCallback((v) => {
6185
+ setQuery(v);
6186
+ if (searchTimer.current) clearTimeout(searchTimer.current);
6187
+ if (!v.trim()) {
6188
+ searchSeq.current++;
6189
+ setMatchPages(null);
6190
+ setMatchIdx(0);
6191
+ setSearching(false);
6192
+ return;
6193
+ }
6194
+ searchTimer.current = setTimeout(() => runSearch(v), SEARCH_DEBOUNCE);
6195
+ }, [runSearch]);
6196
+ useEffect(() => () => {
6197
+ if (searchTimer.current) clearTimeout(searchTimer.current);
6198
+ }, []);
6093
6199
  const gotoMatch = (dir) => {
6094
6200
  if (!matchPages?.length) return;
6095
6201
  const next = (matchIdx + dir + matchPages.length) % matchPages.length;
@@ -6103,15 +6209,9 @@ function PdfViewer({
6103
6209
  if (typeof Blob !== "undefined" && source instanceof Blob) return new Uint8Array(await source.arrayBuffer());
6104
6210
  throw new Error("No bytes available");
6105
6211
  }, [doc, source]);
6106
- const download2 = useCallback(async () => {
6212
+ const download = useCallback(async () => {
6107
6213
  const bytes = await getBytes();
6108
- const blob = new Blob([bytes], { type: "application/pdf" });
6109
- const url = URL.createObjectURL(blob);
6110
- const a = document.createElement("a");
6111
- a.href = url;
6112
- a.download = sourceName(source) || "document.pdf";
6113
- a.click();
6114
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
6214
+ downloadBlob(new Blob([bytes], { type: "application/pdf" }), sourceName(source) || "document.pdf");
6115
6215
  }, [getBytes, source]);
6116
6216
  const print = useCallback(async () => {
6117
6217
  const bytes = await getBytes();
@@ -6137,6 +6237,12 @@ function PdfViewer({
6137
6237
  const cur = typeof zoomMode === "number" ? zoomMode : scale;
6138
6238
  setZoomMode(Math.min(5, Math.max(0.25, +(cur * factor).toFixed(2))));
6139
6239
  };
6240
+ const zoomPct = Math.round(scale * 100);
6241
+ const commitPageDraft = () => {
6242
+ pageEditing.current = false;
6243
+ const n = parseInt(pageDraft, 10);
6244
+ if (Number.isFinite(n)) scrollToPage(n);
6245
+ };
6140
6246
  if (status === "error") {
6141
6247
  return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col items-center justify-center gap-3 rounded-lg border border-border bg-surface p-8 text-center", className), style, children: [
6142
6248
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-status-error", children: "Couldn\u2019t load the PDF" }),
@@ -6144,80 +6250,127 @@ function PdfViewer({
6144
6250
  /* @__PURE__ */ jsx(Button_default, { content: "Retry", size: "sm", variant: "outline", onClick: () => setReloadKey((k) => k + 1) })
6145
6251
  ] });
6146
6252
  }
6147
- return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style: { height: 600, ...style }, children: [
6148
- toolbar !== false && /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 flex-wrap items-center gap-2 border-b border-border bg-surface px-2 py-1.5", children: [
6149
- tb.pager && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
6150
- /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Previous page", onClick: () => scrollToPage(Math.max(1, page - 1)), icon: /* @__PURE__ */ jsx(Chevron5, { dir: "up" }), disabled: status !== "ready" }),
6151
- /* @__PURE__ */ jsxs("span", { className: "px-1 text-xs tabular-nums text-foreground-secondary select-none", children: [
6152
- status === "ready" ? page : "\u2013",
6153
- " / ",
6154
- numPages || "\u2013"
6253
+ const ready = status === "ready";
6254
+ return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style: { height, width, ...style }, children: [
6255
+ toolbar !== false && /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 flex-col border-b border-border bg-surface", children: [
6256
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 px-2 py-1.5", children: [
6257
+ tb.pager && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
6258
+ /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Previous page", onClick: () => scrollToPage(page - 1), icon: /* @__PURE__ */ jsx(Chevron5, { dir: "up" }), disabled: !ready || page <= 1 }),
6259
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-foreground-secondary select-none", children: [
6260
+ /* @__PURE__ */ jsx(
6261
+ "input",
6262
+ {
6263
+ value: pageEditing.current ? pageDraft : ready ? String(page) : "\u2013",
6264
+ onFocus: () => {
6265
+ pageEditing.current = true;
6266
+ setPageDraft(String(page));
6267
+ },
6268
+ onChange: (e) => setPageDraft(e.target.value.replace(/[^\d]/g, "")),
6269
+ onBlur: commitPageDraft,
6270
+ onKeyDown: (e) => {
6271
+ if (e.key === "Enter") e.target.blur();
6272
+ },
6273
+ disabled: !ready,
6274
+ "aria-label": "Page number",
6275
+ className: "h-6 w-9 rounded border border-border bg-surface-raised text-center tabular-nums text-foreground outline-none focus:border-accent disabled:opacity-50"
6276
+ }
6277
+ ),
6278
+ /* @__PURE__ */ jsxs("span", { className: "tabular-nums", children: [
6279
+ "/ ",
6280
+ numPages || "\u2013"
6281
+ ] })
6282
+ ] }),
6283
+ /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Next page", onClick: () => scrollToPage(page + 1), icon: /* @__PURE__ */ jsx(Chevron5, { dir: "down" }), disabled: !ready || page >= numPages })
6155
6284
  ] }),
6156
- /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Next page", onClick: () => scrollToPage(Math.min(numPages, page + 1)), icon: /* @__PURE__ */ jsx(Chevron5, { dir: "down" }), disabled: status !== "ready" })
6157
- ] }),
6158
- tb.zoom && /* @__PURE__ */ jsxs("div", { className: "ml-1 flex items-center gap-1", children: [
6159
- /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Zoom out", onClick: () => setZoomNum(1 / 1.2), icon: /* @__PURE__ */ jsx("span", { className: "text-base leading-none", children: "\u2212" }), disabled: status !== "ready" }),
6160
- /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Zoom in", onClick: () => setZoomNum(1.2), icon: /* @__PURE__ */ jsx("span", { className: "text-base leading-none", children: "+" }), disabled: status !== "ready" }),
6161
- /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Fit width", onClick: () => setZoomMode("page-width"), icon: /* @__PURE__ */ jsx(FitWidthIcon, {}), disabled: status !== "ready" }),
6162
- /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Fit page", onClick: () => setZoomMode("page-fit"), icon: /* @__PURE__ */ jsx(FitPageIcon, {}), disabled: status !== "ready" })
6163
- ] }),
6164
- /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
6165
- tb.search && /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Search", onClick: () => setShowSearch((s) => !s), icon: /* @__PURE__ */ jsx(SearchIcon2, {}), disabled: status !== "ready" }),
6166
- tb.download && /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Download", onClick: download2, icon: /* @__PURE__ */ jsx(DownloadIcon, {}), disabled: status !== "ready" }),
6167
- tb.print && /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Print", onClick: print, icon: /* @__PURE__ */ jsx(PrintIcon, {}), disabled: status !== "ready" })
6285
+ tb.pager && tb.zoom && /* @__PURE__ */ jsx("span", { className: "mx-1 h-5 w-px bg-border", "aria-hidden": "true" }),
6286
+ tb.zoom && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
6287
+ /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Zoom out", onClick: () => setZoomNum(1 / 1.2), icon: /* @__PURE__ */ jsx(MinusIcon, {}), disabled: !ready }),
6288
+ /* @__PURE__ */ jsx("span", { className: "w-10 text-center text-xs tabular-nums text-foreground-secondary select-none", children: ready ? `${zoomPct}%` : "\u2013" }),
6289
+ /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Zoom in", onClick: () => setZoomNum(1.2), icon: /* @__PURE__ */ jsx(PlusIcon, {}), disabled: !ready }),
6290
+ /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Fit width", onClick: () => setZoomMode("page-width"), icon: /* @__PURE__ */ jsx(FitWidthIcon, {}), disabled: !ready }),
6291
+ /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Fit page", onClick: () => setZoomMode("page-fit"), icon: /* @__PURE__ */ jsx(FitPageIcon, {}), disabled: !ready })
6292
+ ] }),
6293
+ /* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-1", children: [
6294
+ tb.search && /* @__PURE__ */ jsx(IconButton_default, { type: showSearch ? "bordered" : "ghost", size: "sm", title: "Search", onClick: () => setShowSearch((s) => !s), icon: /* @__PURE__ */ jsx(SearchIcon2, {}), disabled: !ready }),
6295
+ tb.search && (tb.download || tb.print) && /* @__PURE__ */ jsx("span", { className: "mx-1 h-5 w-px bg-border", "aria-hidden": "true" }),
6296
+ tb.download && /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Download", onClick: download, icon: /* @__PURE__ */ jsx(DownloadIcon, {}), disabled: !ready }),
6297
+ tb.print && /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Print", onClick: print, icon: /* @__PURE__ */ jsx(PrintIcon, {}), disabled: !ready })
6298
+ ] })
6168
6299
  ] }),
6169
- tb.search && showSearch && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2 pt-1", children: [
6300
+ tb.search && showSearch && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t border-border px-2 py-1.5", children: [
6301
+ /* @__PURE__ */ jsx(SearchIcon2, {}),
6170
6302
  /* @__PURE__ */ jsx(
6171
6303
  "input",
6172
6304
  {
6173
6305
  autoFocus: true,
6174
6306
  value: query,
6175
- onChange: (e) => runSearch(e.target.value),
6307
+ onChange: (e) => onQueryChange(e.target.value),
6308
+ onKeyDown: (e) => {
6309
+ if (e.key === "Enter") gotoMatch(e.shiftKey ? -1 : 1);
6310
+ },
6176
6311
  placeholder: "Find in document\u2026",
6177
- className: "h-7 flex-1 rounded-md border border-border bg-surface px-2 text-sm text-foreground outline-none focus:border-accent"
6312
+ className: "h-7 flex-1 bg-transparent text-sm text-foreground outline-none placeholder:text-foreground-muted"
6178
6313
  }
6179
6314
  ),
6180
- /* @__PURE__ */ jsx("span", { className: "text-xs tabular-nums text-foreground-muted", children: matchPages == null ? "" : matchPages.length ? `${matchIdx + 1}/${matchPages.length} pages` : "no matches" }),
6315
+ /* @__PURE__ */ jsx("span", { className: "text-xs tabular-nums text-foreground-muted", children: searching ? "Searching\u2026" : matchPages == null ? "" : matchPages.length ? `${matchIdx + 1} of ${matchPages.length}` : "No matches" }),
6181
6316
  /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Previous match", onClick: () => gotoMatch(-1), icon: /* @__PURE__ */ jsx(Chevron5, { dir: "up" }), disabled: !matchPages?.length }),
6182
- /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Next match", onClick: () => gotoMatch(1), icon: /* @__PURE__ */ jsx(Chevron5, { dir: "down" }), disabled: !matchPages?.length })
6317
+ /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Next match", onClick: () => gotoMatch(1), icon: /* @__PURE__ */ jsx(Chevron5, { dir: "down" }), disabled: !matchPages?.length }),
6318
+ /* @__PURE__ */ jsx(IconButton_default, { type: "ghost", size: "sm", title: "Close search", onClick: () => {
6319
+ setShowSearch(false);
6320
+ onQueryChange("");
6321
+ }, icon: /* @__PURE__ */ jsx(CloseIcon, {}) })
6183
6322
  ] })
6184
6323
  ] }),
6185
6324
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1", children: [
6186
- thumbnails && status === "ready" && doc && baseSize && /* @__PURE__ */ jsx("div", { className: "w-32 flex-shrink-0 overflow-y-auto border-r border-border bg-surface p-2", children: Array.from({ length: numPages }, (_, i) => i + 1).map((p) => /* @__PURE__ */ jsx(
6187
- "button",
6188
- {
6189
- type: "button",
6190
- onClick: () => scrollToPage(p),
6191
- className: cx(
6192
- "mb-2 block w-full overflow-hidden rounded border bg-surface-raised transition-colors",
6193
- p === page ? "border-accent ring-1 ring-accent" : "border-border hover:border-border-strong"
6194
- ),
6195
- style: { aspectRatio: `${baseSize.width} / ${baseSize.height}` },
6196
- "aria-label": `Page ${p}`,
6197
- children: Math.abs(p - page) <= 8 ? /* @__PURE__ */ jsx(PdfPage, { pdfjs, doc, page: p, scale: 112 / baseSize.width, textLayer: false }) : /* @__PURE__ */ jsx("span", { className: "block py-4 text-center text-xs text-foreground-muted", children: p })
6198
- },
6199
- p
6200
- )) }),
6325
+ thumbnails && ready && doc && baseSize && /* @__PURE__ */ jsx("div", { className: "w-32 flex-shrink-0 overflow-y-auto border-r border-border bg-surface p-2", children: Array.from({ length: numPages }, (_, i) => i + 1).map((p) => {
6326
+ const s = sizes[p - 1] ?? baseSize;
6327
+ return /* @__PURE__ */ jsx(
6328
+ "button",
6329
+ {
6330
+ type: "button",
6331
+ onClick: () => scrollToPage(p),
6332
+ className: cx(
6333
+ "mb-2 block w-full overflow-hidden rounded border bg-surface-raised transition-colors",
6334
+ p === page ? "border-accent ring-1 ring-accent" : "border-border hover:border-border-strong"
6335
+ ),
6336
+ style: { aspectRatio: `${s.width} / ${s.height}` },
6337
+ "aria-label": `Page ${p}`,
6338
+ "aria-current": p === page,
6339
+ children: Math.abs(p - page) <= 8 ? /* @__PURE__ */ jsx(PdfPage, { pdfjs, doc, page: p, scale: 112 / s.width, textLayer: false }) : /* @__PURE__ */ jsx("span", { className: "block py-4 text-center text-xs text-foreground-muted", children: p })
6340
+ },
6341
+ p
6342
+ );
6343
+ }) }),
6201
6344
  /* @__PURE__ */ jsx(
6202
6345
  "div",
6203
6346
  {
6204
6347
  ref: scrollRef,
6205
6348
  onScroll: (e) => setScrollTop(e.currentTarget.scrollTop),
6206
6349
  className: "relative flex-1 overflow-auto bg-background",
6207
- children: status === "loading" || !baseSize ? /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center gap-3 p-6", children: /* @__PURE__ */ jsx(SkeletonBox, { width: Math.min(viewport.w - 48, 560) || 480, height: 680, className: "rounded" }) }) : numPages === 0 ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-sm text-foreground-muted", children: "Empty document" }) : /* @__PURE__ */ jsx("div", { style: { height: total, position: "relative" }, children: visiblePages.map((p) => /* @__PURE__ */ jsx(
6208
- "div",
6209
- {
6210
- style: { position: "absolute", top: (p - 1) * pageH, left: 0, right: 0, height: pageH, display: "flex", justifyContent: "center", paddingTop: GAP / 2, paddingBottom: GAP / 2 },
6211
- children: /* @__PURE__ */ jsx("div", { className: "relative shadow-md", style: { width: pageW, height: baseSize.height * scale }, children: /* @__PURE__ */ jsx(PdfPage, { pdfjs, doc, page: p, scale, textLayer }) })
6212
- },
6213
- p
6214
- )) })
6350
+ children: !ready || !baseSize ? /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center gap-3 p-6", children: /* @__PURE__ */ jsx(SkeletonBox, { width: Math.min((viewport.w || 528) - 48, 560), height: 680, className: "rounded" }) }) : numPages === 0 ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center text-sm text-foreground-muted", children: "Empty document" }) : /* @__PURE__ */ jsx("div", { style: { height: total, position: "relative" }, children: visiblePages.map((p) => {
6351
+ const s = sizes[p - 1] ?? baseSize;
6352
+ return /* @__PURE__ */ jsx(
6353
+ "div",
6354
+ {
6355
+ style: { position: "absolute", top: offsets[p - 1], left: 0, right: 0, display: "flex", justifyContent: "center", paddingTop: GAP / 2 },
6356
+ children: /* @__PURE__ */ jsx("div", { className: "relative shadow-md", style: { width: s.width * scale, height: s.height * scale }, children: /* @__PURE__ */ jsx(PdfPage, { pdfjs, doc, page: p, scale, textLayer, onMeasure: handleMeasure }) })
6357
+ },
6358
+ p
6359
+ );
6360
+ }) })
6215
6361
  }
6216
6362
  )
6217
6363
  ] })
6218
6364
  ] });
6219
6365
  }
6220
- function PdfPage({ pdfjs, doc, page, scale, textLayer }) {
6366
+ function PdfPage({
6367
+ pdfjs,
6368
+ doc,
6369
+ page,
6370
+ scale,
6371
+ textLayer,
6372
+ onMeasure
6373
+ }) {
6221
6374
  const canvasRef = useRef(null);
6222
6375
  const textRef = useRef(null);
6223
6376
  useEffect(() => {
@@ -6227,6 +6380,7 @@ function PdfPage({ pdfjs, doc, page, scale, textLayer }) {
6227
6380
  const pg = await doc.getPage(page);
6228
6381
  if (cancelled) return;
6229
6382
  const viewport = pg.getViewport({ scale });
6383
+ onMeasure?.(page, { width: viewport.width / scale, height: viewport.height / scale });
6230
6384
  const canvas = canvasRef.current;
6231
6385
  if (!canvas) return;
6232
6386
  const ratio = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
@@ -6243,6 +6397,7 @@ function PdfPage({ pdfjs, doc, page, scale, textLayer }) {
6243
6397
  }
6244
6398
  if (cancelled || !textLayer || !textRef.current || !pdfjs?.TextLayer) return;
6245
6399
  try {
6400
+ ensureTextLayerStyles();
6246
6401
  textRef.current.innerHTML = "";
6247
6402
  textRef.current.style.setProperty("--scale-factor", String(scale));
6248
6403
  const tl = new pdfjs.TextLayer({ textContentSource: pg.streamTextContent(), container: textRef.current, viewport });
@@ -6254,17 +6409,20 @@ function PdfPage({ pdfjs, doc, page, scale, textLayer }) {
6254
6409
  cancelled = true;
6255
6410
  renderTask?.cancel?.();
6256
6411
  };
6257
- }, [pdfjs, doc, page, scale, textLayer]);
6412
+ }, [pdfjs, doc, page, scale, textLayer, onMeasure]);
6258
6413
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6259
6414
  /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "block bg-white" }),
6260
- textLayer && /* @__PURE__ */ jsx("div", { ref: textRef, className: "textLayer pointer-events-auto absolute inset-0 overflow-hidden leading-none", style: { opacity: 0.25 }, "aria-hidden": "true" })
6415
+ textLayer && /* @__PURE__ */ jsx("div", { ref: textRef, className: "oxy-textLayer", "aria-hidden": "true" })
6261
6416
  ] });
6262
6417
  }
6263
6418
  var Chevron5 = ({ dir }) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: dir === "up" ? "m6 15 6-6 6 6" : "m6 9 6 6 6-6" }) });
6264
- var SearchIcon2 = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: [
6419
+ var MinusIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M5 12h14" }) });
6420
+ var PlusIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M12 5v14M5 12h14" }) });
6421
+ var SearchIcon2 = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4 text-foreground-muted", "aria-hidden": "true", children: [
6265
6422
  /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
6266
6423
  /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "m21 21-4.3-4.3" })
6267
6424
  ] });
6425
+ var CloseIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M6 6l12 12M18 6 6 18" }) });
6268
6426
  var DownloadIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 3v12m0 0 4-4m-4 4-4-4M4 21h16" }) });
6269
6427
  var PrintIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 9V3h12v6M6 18H4v-7h16v7h-2M8 14h8v7H8z" }) });
6270
6428
  var FitWidthIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 12h18m0 0-4-4m4 4-4 4M3 12l4-4m-4 4 4 4" }) });
@@ -6274,11 +6432,20 @@ var FitPageIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill
6274
6432
  ] });
6275
6433
  var DEFAULT_COL_WIDTH = 140;
6276
6434
  var GUTTER = 52;
6435
+ var END_PAD = 12;
6436
+ function compareValues2(a, b) {
6437
+ if (a == null && b == null) return 0;
6438
+ if (a == null) return 1;
6439
+ if (b == null) return -1;
6440
+ if (typeof a === "number" && typeof b === "number") return a - b;
6441
+ if (typeof a === "boolean" && typeof b === "boolean") return a === b ? 0 : a ? 1 : -1;
6442
+ return String(a).localeCompare(String(b), void 0, { numeric: true, sensitivity: "base" });
6443
+ }
6277
6444
  function resolveWidth(w) {
6278
- if (typeof w === "number") return w;
6445
+ if (typeof w === "number" && Number.isFinite(w)) return w;
6279
6446
  if (typeof w === "string") {
6280
- const n = parseFloat(w);
6281
- if (!Number.isNaN(n) && /^\d/.test(w.trim())) return n;
6447
+ const m = /^\s*(\d+(?:\.\d+)?)(px)?\s*$/.exec(w);
6448
+ if (m) return parseFloat(m[1]);
6282
6449
  }
6283
6450
  return DEFAULT_COL_WIDTH;
6284
6451
  }
@@ -6292,7 +6459,11 @@ function DataGrid({
6292
6459
  rowHeight = 34,
6293
6460
  headerHeight = 38,
6294
6461
  height = 480,
6462
+ width,
6295
6463
  editable = false,
6464
+ sortable = false,
6465
+ sort: sortProp,
6466
+ onSortChange,
6296
6467
  virtualize = true,
6297
6468
  overscan = 4,
6298
6469
  rowNumbers = true,
@@ -6306,7 +6477,10 @@ function DataGrid({
6306
6477
  const [viewport, setViewport] = useState({ w: 0, h: 0 });
6307
6478
  const [editing, setEditing] = useState(null);
6308
6479
  const [draft, setDraft] = useState("");
6480
+ const [internalSort, setInternalSort] = useState(null);
6481
+ const sort = sortProp !== void 0 ? sortProp : internalSort;
6309
6482
  const gutter = rowNumbers ? GUTTER : 0;
6483
+ const colSortable = (c) => c.sortable ?? sortable;
6310
6484
  const { widths, offsets, totalWidth } = useMemo(() => {
6311
6485
  const widths2 = columns.map((c) => resolveWidth(c.width));
6312
6486
  const offsets2 = [];
@@ -6317,6 +6491,18 @@ function DataGrid({
6317
6491
  }
6318
6492
  return { widths: widths2, offsets: offsets2, totalWidth: acc };
6319
6493
  }, [columns]);
6494
+ const order = useMemo(() => {
6495
+ const idx = rows.map((_, i) => i);
6496
+ if (!sort) return idx;
6497
+ const dir = sort.dir === "asc" ? 1 : -1;
6498
+ return idx.sort((ia, ib) => dir * compareValues2(rows[ia]?.[sort.key] ?? null, rows[ib]?.[sort.key] ?? null));
6499
+ }, [rows, sort]);
6500
+ const toggleSort = (key) => {
6501
+ const nextDir = sort?.key !== key ? "asc" : sort.dir === "asc" ? "desc" : null;
6502
+ const next = nextDir ? { key, dir: nextDir } : null;
6503
+ if (sortProp === void 0) setInternalSort(next);
6504
+ onSortChange?.(next);
6505
+ };
6320
6506
  const totalHeight = rows.length * rowHeight;
6321
6507
  useEffect(() => {
6322
6508
  const el = scrollRef.current;
@@ -6347,14 +6533,14 @@ function DataGrid({
6347
6533
  const commit = useCallback(() => {
6348
6534
  if (!editing) return;
6349
6535
  const col = columns[editing.col];
6350
- onCellEdit?.({ row: editing.row, column: col.key, value: draft });
6536
+ onCellEdit?.({ row: order[editing.disp], column: col.key, value: draft });
6351
6537
  setEditing(null);
6352
- }, [editing, columns, draft, onCellEdit]);
6353
- const startEdit = (row, col) => {
6538
+ }, [editing, columns, draft, onCellEdit, order]);
6539
+ const startEdit = (disp, col) => {
6354
6540
  const c = columns[col];
6355
6541
  if (!(c.editable ?? editable)) return;
6356
- setDraft(displayValue(rows[row]?.[c.key] ?? ""));
6357
- setEditing({ row, col });
6542
+ setDraft(displayValue(rows[order[disp]]?.[c.key] ?? ""));
6543
+ setEditing({ disp, col });
6358
6544
  };
6359
6545
  return /* @__PURE__ */ jsxs(
6360
6546
  "div",
@@ -6362,12 +6548,12 @@ function DataGrid({
6362
6548
  ref: scrollRef,
6363
6549
  onScroll: (e) => setScroll({ top: e.currentTarget.scrollTop, left: e.currentTarget.scrollLeft }),
6364
6550
  className: cx("relative overflow-auto rounded-lg border border-border bg-surface-raised text-sm", className),
6365
- style: { height, ...style },
6551
+ style: { height, width, ...style },
6366
6552
  role: "grid",
6367
6553
  "aria-rowcount": rows.length,
6368
6554
  "aria-colcount": columns.length,
6369
6555
  children: [
6370
- /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: gutter + totalWidth, height: headerHeight + totalHeight }, children: [
6556
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: gutter + totalWidth + END_PAD, height: headerHeight + totalHeight + END_PAD }, children: [
6371
6557
  rowNumbers && /* @__PURE__ */ jsx(
6372
6558
  "div",
6373
6559
  {
@@ -6377,11 +6563,18 @@ function DataGrid({
6377
6563
  ),
6378
6564
  visibleCols.map((ci) => {
6379
6565
  const c = columns[ci];
6380
- return /* @__PURE__ */ jsx(
6566
+ const sortDir = sort?.key === c.key ? sort.dir : null;
6567
+ const canSort = colSortable(c);
6568
+ return /* @__PURE__ */ jsxs(
6381
6569
  "div",
6382
6570
  {
6383
6571
  role: "columnheader",
6384
- className: "flex items-center border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6572
+ "aria-sort": sortDir ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6573
+ onClick: canSort ? () => toggleSort(c.key) : void 0,
6574
+ className: cx(
6575
+ "flex items-center gap-1 border-b border-r border-border bg-surface px-3 font-medium text-foreground-secondary",
6576
+ canSort && "cursor-pointer select-none hover:text-foreground"
6577
+ ),
6385
6578
  style: {
6386
6579
  position: "absolute",
6387
6580
  top: scroll.top,
@@ -6391,38 +6584,42 @@ function DataGrid({
6391
6584
  zIndex: 2,
6392
6585
  justifyContent: c.align === "right" ? "flex-end" : c.align === "center" ? "center" : "flex-start"
6393
6586
  },
6394
- children: /* @__PURE__ */ jsx("span", { className: "truncate", children: c.label ?? c.key })
6587
+ children: [
6588
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: c.label ?? c.key }),
6589
+ canSort && /* @__PURE__ */ jsx(SortCaret, { dir: sortDir })
6590
+ ]
6395
6591
  },
6396
6592
  `h-${c.key}`
6397
6593
  );
6398
6594
  }),
6399
- rowNumbers && visibleRows.map((ri) => /* @__PURE__ */ jsx(
6595
+ rowNumbers && visibleRows.map((disp) => /* @__PURE__ */ jsx(
6400
6596
  "div",
6401
6597
  {
6402
6598
  className: "flex items-center justify-center border-b border-r border-border bg-surface text-xs tabular-nums text-foreground-muted",
6403
- style: { position: "absolute", left: scroll.left, top: headerHeight + ri * rowHeight, width: gutter, height: rowHeight, zIndex: 1 },
6404
- children: ri + 1
6599
+ style: { position: "absolute", left: scroll.left, top: headerHeight + disp * rowHeight, width: gutter, height: rowHeight, zIndex: 1 },
6600
+ children: disp + 1
6405
6601
  },
6406
- `g-${ri}`
6602
+ `g-${disp}`
6407
6603
  )),
6408
- visibleRows.map(
6409
- (ri) => visibleCols.map((ci) => {
6604
+ visibleRows.map((disp) => {
6605
+ const ri = order[disp];
6606
+ return visibleCols.map((ci) => {
6410
6607
  const c = columns[ci];
6411
- const isEditing = editing?.row === ri && editing?.col === ci;
6608
+ const isEditing = editing?.disp === disp && editing?.col === ci;
6412
6609
  const canEdit = c.editable ?? editable;
6413
6610
  return /* @__PURE__ */ jsx(
6414
6611
  "div",
6415
6612
  {
6416
6613
  role: "gridcell",
6417
- onDoubleClick: () => startEdit(ri, ci),
6614
+ onDoubleClick: () => startEdit(disp, ci),
6418
6615
  className: cx(
6419
6616
  "flex items-center border-b border-r border-border px-3",
6420
- ri % 2 ? "bg-surface-raised" : "bg-surface",
6617
+ disp % 2 ? "bg-surface-raised" : "bg-surface",
6421
6618
  canEdit && "cursor-text"
6422
6619
  ),
6423
6620
  style: {
6424
6621
  position: "absolute",
6425
- top: headerHeight + ri * rowHeight,
6622
+ top: headerHeight + disp * rowHeight,
6426
6623
  left: gutter + offsets[ci],
6427
6624
  width: widths[ci],
6428
6625
  height: rowHeight,
@@ -6443,16 +6640,22 @@ function DataGrid({
6443
6640
  }
6444
6641
  ) : /* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: displayValue(rows[ri]?.[c.key] ?? "") })
6445
6642
  },
6446
- `${ri}-${c.key}`
6643
+ `${disp}-${c.key}`
6447
6644
  );
6448
- })
6449
- )
6645
+ });
6646
+ })
6450
6647
  ] }),
6451
6648
  rows.length === 0 && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-foreground-muted", style: { top: headerHeight }, children: emptyState })
6452
6649
  ]
6453
6650
  }
6454
6651
  );
6455
6652
  }
6653
+ function SortCaret({ dir }) {
6654
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", className: "h-3 w-3 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 1.75, "aria-hidden": "true", children: [
6655
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 6.5 8 3.5l3 3", className: dir === "asc" ? "text-accent" : "text-foreground-muted opacity-50" }),
6656
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 9.5 8 12.5l3-3", className: dir === "desc" ? "text-accent" : "text-foreground-muted opacity-50" })
6657
+ ] });
6658
+ }
6456
6659
  function toColumns(cols) {
6457
6660
  return cols.map((c) => typeof c === "string" ? { key: c, label: c } : { ...c, label: c.label ?? c.key });
6458
6661
  }
@@ -6471,13 +6674,16 @@ var xlsxPromise = null;
6471
6674
  var loadXlsx = () => xlsxPromise ??= import('xlsx');
6472
6675
  var jspdfPromise = null;
6473
6676
  var loadJspdf = () => jspdfPromise ??= import('jspdf');
6474
- function download(blob, name) {
6475
- const url = URL.createObjectURL(blob);
6476
- const a = document.createElement("a");
6477
- a.href = url;
6478
- a.download = name;
6479
- a.click();
6480
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
6677
+ function coerceToCellType(prev, next) {
6678
+ if (typeof prev === "number") {
6679
+ const n = Number(next);
6680
+ return next.trim() !== "" && Number.isFinite(n) ? n : next;
6681
+ }
6682
+ if (typeof prev === "boolean") {
6683
+ if (/^(true|false)$/i.test(next.trim())) return next.trim().toLowerCase() === "true";
6684
+ return next;
6685
+ }
6686
+ return next;
6481
6687
  }
6482
6688
  function Spreadsheet({
6483
6689
  source,
@@ -6488,6 +6694,9 @@ function Spreadsheet({
6488
6694
  export: exportFormats = ["xlsx", "csv", "pdf"],
6489
6695
  fileName,
6490
6696
  virtualize = true,
6697
+ sortable = true,
6698
+ height = 480,
6699
+ width,
6491
6700
  className = "",
6492
6701
  style
6493
6702
  }) {
@@ -6545,20 +6754,23 @@ function Spreadsheet({
6545
6754
  const columns = useMemo(() => sheet ? toColumns(sheet.columns) : [], [sheet]);
6546
6755
  const plainRows = useMemo(() => sheet ? toPlainRows(sheet, columns) : [], [sheet, columns]);
6547
6756
  const handleCellEdit = useCallback(({ row, column, value }) => {
6757
+ let coerced = value;
6548
6758
  setSheets((prev) => {
6549
6759
  if (!prev) return prev;
6550
6760
  const next = prev.map((s, i) => i === active ? { ...s, rows: s.rows.map((r) => ({ ...r })) } : s);
6551
6761
  const target = next[active];
6552
6762
  const existing = target.rows[row]?.[column];
6763
+ const prevValue = cellValue(existing);
6764
+ coerced = coerceToCellType(prevValue, value);
6553
6765
  if (existing != null && typeof existing === "object" && "value" in existing) {
6554
- target.rows[row][column] = { ...existing, value };
6766
+ target.rows[row][column] = { ...existing, value: coerced };
6555
6767
  } else {
6556
- target.rows[row][column] = value;
6768
+ target.rows[row][column] = coerced;
6557
6769
  }
6558
6770
  onChange?.(next);
6559
6771
  return next;
6560
6772
  });
6561
- onCellEdit?.({ sheet: sheets?.[active]?.name ?? "", row, column, value });
6773
+ onCellEdit?.({ sheet: sheets?.[active]?.name ?? "", row, column, value: coerced });
6562
6774
  }, [active, onCellEdit, onChange, sheets]);
6563
6775
  const baseName = useMemo(
6564
6776
  () => (fileName || (typeof source === "object" && "name" in source ? sourceName(source) : null) || "spreadsheet").replace(/\.[^.]+$/, ""),
@@ -6579,7 +6791,7 @@ function Spreadsheet({
6579
6791
  XLSX.utils.book_append_sheet(wb, ws, (s.name || "Sheet").slice(0, 31));
6580
6792
  });
6581
6793
  const out = XLSX.write(wb, { bookType: "xlsx", type: "array" });
6582
- download(new Blob([out], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }), `${baseName}.xlsx`);
6794
+ downloadBlob(new Blob([out], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }), `${baseName}.xlsx`);
6583
6795
  }, [sheets, sheetAoa, baseName]);
6584
6796
  const exportCsv = useCallback(() => {
6585
6797
  if (!sheet) return;
@@ -6589,7 +6801,7 @@ function Spreadsheet({
6589
6801
  return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
6590
6802
  };
6591
6803
  const csv = aoa.map((row) => row.map(esc).join(",")).join("\r\n");
6592
- download(new Blob([`\uFEFF${csv}`], { type: "text/csv;charset=utf-8" }), `${baseName}.csv`);
6804
+ downloadBlob(new Blob([`\uFEFF${csv}`], { type: "text/csv;charset=utf-8" }), `${baseName}.csv`);
6593
6805
  }, [sheet, sheetAoa, baseName]);
6594
6806
  const exportPdf = useCallback(async () => {
6595
6807
  if (!sheet) return;
@@ -6632,50 +6844,87 @@ function Spreadsheet({
6632
6844
  else void exportPdf();
6633
6845
  };
6634
6846
  if (status === "error") {
6635
- return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col items-center justify-center gap-3 rounded-lg border border-border bg-surface p-8 text-center", className), style, children: [
6847
+ return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col items-center justify-center gap-3 rounded-lg border border-border bg-surface p-8 text-center", className), style: { height, width, ...style }, children: [
6636
6848
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-status-error", children: "Couldn\u2019t load the spreadsheet" }),
6637
6849
  error?.message && /* @__PURE__ */ jsx("p", { className: "max-w-md text-xs text-foreground-muted", children: error.message }),
6638
6850
  /* @__PURE__ */ jsx(Button_default, { content: "Retry", size: "sm", variant: "outline", onClick: () => setReloadKey((k) => k + 1) })
6639
6851
  ] });
6640
6852
  }
6641
6853
  if (status === "loading" || !sheets) {
6642
- return /* @__PURE__ */ jsxs("div", { className: cx("rounded-lg border border-border bg-surface-raised p-4", className), style, children: [
6854
+ return /* @__PURE__ */ jsxs("div", { className: cx("overflow-hidden rounded-lg border border-border bg-surface-raised p-4", className), style: { height, width, ...style }, children: [
6643
6855
  /* @__PURE__ */ jsx(SkeletonBox, { height: 32, className: "mb-3 rounded" }),
6644
- /* @__PURE__ */ jsx(SkeletonBox, { height: 360, className: "rounded" })
6856
+ /* @__PURE__ */ jsx(SkeletonBox, { height: "calc(100% - 44px)", className: "rounded" })
6645
6857
  ] });
6646
6858
  }
6647
6859
  const formats = exportFormats || [];
6648
- return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col gap-2", className), style, children: [
6649
- (sheets.length > 1 || formats.length > 0) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
6650
- sheets.length > 1 && /* @__PURE__ */ jsx("div", { role: "tablist", className: "flex flex-wrap items-center gap-1 rounded-lg border border-border bg-surface p-1", children: sheets.map((s, i) => /* @__PURE__ */ jsx(
6651
- "button",
6652
- {
6653
- role: "tab",
6654
- type: "button",
6655
- "aria-selected": i === active,
6656
- onClick: () => setActive(i),
6657
- className: cx(
6658
- "rounded-md px-3 py-1 text-sm transition-colors",
6659
- i === active ? "bg-accent text-accent-fg" : "text-foreground-secondary hover:bg-surface-raised hover:text-foreground"
6660
- ),
6661
- children: s.name || `Sheet ${i + 1}`
6662
- },
6663
- `${s.name}-${i}`
6664
- )) }),
6665
- formats.length > 0 && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-1.5", children: formats.map((fmt) => /* @__PURE__ */ jsx(Button_default, { content: fmt.toUpperCase(), size: "sm", variant: "outline", onClick: () => runExport(fmt) }, fmt)) })
6860
+ const exportLabels = {
6861
+ xlsx: "Excel workbook (.xlsx)",
6862
+ csv: "CSV \u2014 this sheet (.csv)",
6863
+ pdf: "PDF table (.pdf)"
6864
+ };
6865
+ return /* @__PURE__ */ jsxs("div", { className: cx("flex flex-col overflow-hidden rounded-lg border border-border bg-surface-raised", className), style: { height, width, ...style }, children: [
6866
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-shrink-0 items-center gap-2 border-b border-border bg-surface px-3 py-1.5", children: [
6867
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-foreground", children: sheet?.name || "Sheet 1" }),
6868
+ /* @__PURE__ */ jsxs("span", { className: "hidden flex-shrink-0 text-xs tabular-nums text-foreground-muted sm:inline", children: [
6869
+ plainRows.length.toLocaleString(),
6870
+ " ",
6871
+ plainRows.length === 1 ? "row" : "rows",
6872
+ " \xB7 ",
6873
+ columns.length,
6874
+ " cols"
6875
+ ] }),
6876
+ formats.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
6877
+ /* @__PURE__ */ jsx("span", { className: "h-5 w-px flex-shrink-0 bg-border", "aria-hidden": "true" }),
6878
+ /* @__PURE__ */ jsx(
6879
+ MenuButton,
6880
+ {
6881
+ label: "Export",
6882
+ size: "sm",
6883
+ variant: "outline",
6884
+ align: "end",
6885
+ icon: /* @__PURE__ */ jsx(DownloadIcon2, {}),
6886
+ items: formats.map((fmt) => ({
6887
+ key: fmt,
6888
+ label: exportLabels[fmt],
6889
+ onSelect: () => runExport(fmt)
6890
+ }))
6891
+ }
6892
+ )
6893
+ ] })
6666
6894
  ] }),
6667
- /* @__PURE__ */ jsx(
6895
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(
6668
6896
  DataGrid,
6669
6897
  {
6670
6898
  columns,
6671
6899
  rows: plainRows,
6672
6900
  editable,
6901
+ sortable,
6673
6902
  virtualize,
6674
- onCellEdit: handleCellEdit
6675
- }
6676
- )
6903
+ onCellEdit: handleCellEdit,
6904
+ height: "100%",
6905
+ className: "!rounded-none !border-0"
6906
+ },
6907
+ active
6908
+ ) }),
6909
+ sheets.length > 1 && /* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": "Sheets", className: "flex flex-shrink-0 items-center gap-1 overflow-x-auto border-t border-border bg-surface px-2 py-1", children: sheets.map((s, i) => /* @__PURE__ */ jsx(
6910
+ "button",
6911
+ {
6912
+ role: "tab",
6913
+ type: "button",
6914
+ "aria-selected": i === active,
6915
+ onClick: () => setActive(i),
6916
+ className: cx(
6917
+ "flex-shrink-0 rounded-md px-3 py-1 text-xs font-medium transition-colors",
6918
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent",
6919
+ i === active ? "bg-surface-raised text-foreground shadow-sm" : "text-foreground-secondary hover:bg-surface-raised hover:text-foreground"
6920
+ ),
6921
+ children: s.name || `Sheet ${i + 1}`
6922
+ },
6923
+ `${s.name}-${i}`
6924
+ )) })
6677
6925
  ] });
6678
6926
  }
6927
+ var DownloadIcon2 = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, className: "h-4 w-4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 3v12m0 0 4-4m-4 4-4-4M4 21h16" }) });
6679
6928
  function ThemeSwitch({ checked, onChange, label = "Toggle dark mode", className = "" }) {
6680
6929
  const id = useId();
6681
6930
  return /* @__PURE__ */ jsx("label", { htmlFor: id, className: `flex items-center gap-2 cursor-pointer select-none ${className}`.trim(), children: /* @__PURE__ */ jsx(
@@ -6776,7 +7025,7 @@ function FlyoutItems({ items }) {
6776
7025
  }
6777
7026
  function CollapsedFlyout({ item, trigger }) {
6778
7027
  const [open, setOpen] = useState(false);
6779
- const closeTimer = React34.useRef(void 0);
7028
+ const closeTimer = React36.useRef(void 0);
6780
7029
  const openNow = () => {
6781
7030
  window.clearTimeout(closeTimer.current);
6782
7031
  setOpen(true);
@@ -6785,7 +7034,7 @@ function CollapsedFlyout({ item, trigger }) {
6785
7034
  window.clearTimeout(closeTimer.current);
6786
7035
  closeTimer.current = window.setTimeout(() => setOpen(false), 150);
6787
7036
  };
6788
- React34.useEffect(() => () => window.clearTimeout(closeTimer.current), []);
7037
+ React36.useEffect(() => () => window.clearTimeout(closeTimer.current), []);
6789
7038
  return /* @__PURE__ */ jsxs(DropdownMenu2.Root, { open, onOpenChange: setOpen, modal: false, children: [
6790
7039
  /* @__PURE__ */ jsx(
6791
7040
  DropdownMenu2.Trigger,
@@ -7077,8 +7326,8 @@ function MegaMenuLink({ href, icon, description, active, onClick, children, clas
7077
7326
  function MegaMenuFeatured({ children, className = "" }) {
7078
7327
  return /* @__PURE__ */ jsx("div", { className: cx("min-w-0 rounded-lg bg-surface-raised border border-border p-4 flex flex-col", className), children });
7079
7328
  }
7080
- var elementsOfType = (children, type) => React34.Children.toArray(children).filter(
7081
- (c) => React34.isValidElement(c) && c.type === type
7329
+ var elementsOfType = (children, type) => React36.Children.toArray(children).filter(
7330
+ (c) => React36.isValidElement(c) && c.type === type
7082
7331
  );
7083
7332
  var MOBILE_CHEVRON = /* @__PURE__ */ jsx(
7084
7333
  "svg",
@@ -7115,9 +7364,9 @@ function MobileLinkRow({ link, onNavigate }) {
7115
7364
  );
7116
7365
  }
7117
7366
  function MobilePanel({ panel, onNavigate }) {
7118
- const nodes = React34.Children.toArray(panel.props.children);
7367
+ const nodes = React36.Children.toArray(panel.props.children);
7119
7368
  return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4 px-2 pb-3 pt-1", children: nodes.map((node, i) => {
7120
- if (!React34.isValidElement(node)) return null;
7369
+ if (!React36.isValidElement(node)) return null;
7121
7370
  const el = node;
7122
7371
  if (el.type === MegaMenuSection) {
7123
7372
  const { title, children } = el.props;
@@ -7526,7 +7775,7 @@ function ThemeProvider({
7526
7775
  className = "",
7527
7776
  style
7528
7777
  }) {
7529
- const id = React34.useId().replace(/:/g, "");
7778
+ const id = React36.useId().replace(/:/g, "");
7530
7779
  const scopeClass = `geo-th-${id}`;
7531
7780
  const divRef = useRef(null);
7532
7781
  useEffect(() => {
@@ -8339,8 +8588,11 @@ function Dropdown({
8339
8588
  labelStyle = {},
8340
8589
  placeholder,
8341
8590
  size = "md",
8342
- className = ""
8591
+ className = "",
8592
+ container
8343
8593
  }) {
8594
+ const dialogContainer = useDialogContainer();
8595
+ const portalContainer = container ?? dialogContainer ?? void 0;
8344
8596
  const [open, setOpen] = useState(false);
8345
8597
  const [selectedItems, setSelectedItems] = useState([]);
8346
8598
  const [searchTerm, setSearchTerm] = useState("");
@@ -8446,7 +8698,7 @@ function Dropdown({
8446
8698
  ]
8447
8699
  }
8448
8700
  ) }),
8449
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(
8701
+ /* @__PURE__ */ jsx(Popover.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
8450
8702
  Popover.Content,
8451
8703
  {
8452
8704
  align: "start",
@@ -9834,7 +10086,7 @@ function OtpInput({
9834
10086
  emit(valid.join(""));
9835
10087
  focusBox(valid.length);
9836
10088
  };
9837
- return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
10089
+ return /* @__PURE__ */ jsx(Field, { className, label, htmlFor, errorId, errorMessage, required, layout, helperText, children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", role: "group", "aria-label": typeof label === "string" ? label : "One-time code", children: chars.map((char, idx) => /* @__PURE__ */ jsxs(React36.Fragment, { children: [
9838
10090
  /* @__PURE__ */ jsx(
9839
10091
  "input",
9840
10092
  {
@@ -10893,7 +11145,7 @@ function Blog({
10893
11145
  post.tag != null && !post.image && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Badge, { tone: "accent", variant: "soft", size: "sm", children: post.tag }) }),
10894
11146
  /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold leading-snug text-foreground transition-colors group-hover:text-accent", children: post.title }),
10895
11147
  post.excerpt != null && /* @__PURE__ */ jsx("p", { className: "line-clamp-3 text-sm leading-relaxed text-foreground-secondary", children: post.excerpt }),
10896
- meta.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-auto flex flex-wrap items-center gap-x-2 gap-y-1 pt-3 text-xs text-foreground-muted", children: meta.map((m, j) => /* @__PURE__ */ jsxs(React34.Fragment, { children: [
11148
+ meta.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-auto flex flex-wrap items-center gap-x-2 gap-y-1 pt-3 text-xs text-foreground-muted", children: meta.map((m, j) => /* @__PURE__ */ jsxs(React36.Fragment, { children: [
10897
11149
  j > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
10898
11150
  /* @__PURE__ */ jsx("span", { children: m })
10899
11151
  ] }, j)) })