@almadar/ui 5.13.2 → 5.14.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.
@@ -10,9 +10,9 @@ import { Loader2, X, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil
10
10
  import * as PhosphorIcons from '@phosphor-icons/react';
11
11
  import * as TablerIcons from '@tabler/icons-react';
12
12
  import * as FaIcons from 'react-icons/fa';
13
+ import { createPortal } from 'react-dom';
13
14
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
14
15
  import { useUISlots, UISlotProvider } from '@almadar/ui/context';
15
- import { createPortal } from 'react-dom';
16
16
  import { Link, Outlet, useLocation } from 'react-router-dom';
17
17
  import ELK from 'elkjs/lib/elk.bundled.js';
18
18
  import ReactMarkdown from 'react-markdown';
@@ -2227,7 +2227,7 @@ var init_Modal = __esm({
2227
2227
  document.body.style.overflow = "";
2228
2228
  };
2229
2229
  }, [isOpen]);
2230
- if (!isOpen) return null;
2230
+ if (!isOpen || typeof document === "undefined") return null;
2231
2231
  const handleClose = () => {
2232
2232
  if (closeEvent) eventBus.emit(`UI:${closeEvent}`, {});
2233
2233
  onClose();
@@ -2237,124 +2237,127 @@ var init_Modal = __esm({
2237
2237
  handleClose();
2238
2238
  }
2239
2239
  };
2240
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2241
- /* @__PURE__ */ jsx(
2242
- Overlay,
2243
- {
2244
- isVisible: isOpen,
2245
- onClick: handleOverlayClick,
2246
- className: "z-40"
2247
- }
2248
- ),
2249
- /* @__PURE__ */ jsx(
2250
- Box,
2251
- {
2252
- className: cn(
2253
- "fixed inset-0 z-50 pointer-events-none",
2254
- "flex items-start justify-center px-4 pb-4 pt-[10vh]",
2255
- "max-sm:items-stretch max-sm:p-0 max-sm:pt-0"
2256
- ),
2257
- children: /* @__PURE__ */ jsxs(
2258
- Dialog,
2259
- {
2260
- ref: modalRef,
2261
- open: true,
2262
- className: cn(
2263
- // Reset browser-default dialog chrome — we own styling. `static`
2264
- // overrides the user-agent `position: absolute` so the parent
2265
- // flex container's `justify-center` actually centers the dialog
2266
- // (without this, the dialog drops out of flex flow and `m-0`
2267
- // kills the user-agent's `margin: auto` centering, pinning the
2268
- // dialog to top-left).
2269
- "static m-0 p-0 border-0 bg-transparent",
2270
- // Pre-existing dialog frame
2271
- "pointer-events-auto w-full flex flex-col bg-surface border shadow-elevation-dialog rounded-container",
2272
- // Desktop sizing + viewport-aware floor.
2273
- sizeClasses2[size],
2274
- minWidthClasses[size],
2275
- "max-h-[80vh]",
2276
- // Mobile: take the entire screen. Override desktop max-w cap,
2277
- // full height, no rounded corners, no min-width.
2278
- "max-sm:max-w-none max-sm:max-h-none max-sm:w-full max-sm:h-full max-sm:rounded-none",
2279
- lookStyles[look],
2280
- className
2281
- ),
2282
- style: dragY > 0 ? {
2283
- transform: `translateY(${dragY}px)`,
2284
- transition: isDragging.current ? "none" : "transform 200ms ease-out"
2285
- } : void 0,
2286
- ...title && { "aria-labelledby": "modal-title" },
2287
- children: [
2288
- /* @__PURE__ */ jsx(
2289
- Box,
2290
- {
2291
- className: "hidden max-sm:flex justify-center py-2 cursor-grab active:cursor-grabbing touch-none",
2292
- onPointerDown: (e) => {
2293
- if (!swipeDownToClose) return;
2294
- dragStartY.current = e.clientY;
2295
- isDragging.current = true;
2296
- e.target.setPointerCapture(e.pointerId);
2297
- },
2298
- onPointerMove: (e) => {
2299
- if (!isDragging.current) return;
2300
- const dy = Math.max(0, e.clientY - dragStartY.current);
2301
- setDragY(dy);
2302
- },
2303
- onPointerUp: () => {
2304
- if (!isDragging.current) return;
2305
- isDragging.current = false;
2306
- if (dragY > 100) {
2307
- handleClose();
2308
- }
2309
- setDragY(0);
2310
- },
2311
- onPointerCancel: () => {
2312
- isDragging.current = false;
2313
- setDragY(0);
2314
- },
2315
- children: /* @__PURE__ */ jsx(Box, { className: "w-10 h-1 rounded-full bg-border" })
2316
- }
2240
+ return createPortal(
2241
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2242
+ /* @__PURE__ */ jsx(
2243
+ Overlay,
2244
+ {
2245
+ isVisible: isOpen,
2246
+ onClick: handleOverlayClick,
2247
+ className: "z-[1000]"
2248
+ }
2249
+ ),
2250
+ /* @__PURE__ */ jsx(
2251
+ Box,
2252
+ {
2253
+ className: cn(
2254
+ "fixed inset-0 z-[1001] pointer-events-none",
2255
+ "flex items-start justify-center px-4 pb-4 pt-[10vh]",
2256
+ "max-sm:items-stretch max-sm:p-0 max-sm:pt-0"
2257
+ ),
2258
+ children: /* @__PURE__ */ jsxs(
2259
+ Dialog,
2260
+ {
2261
+ ref: modalRef,
2262
+ open: true,
2263
+ className: cn(
2264
+ // Reset browser-default dialog chrome we own styling. `static`
2265
+ // overrides the user-agent `position: absolute` so the parent
2266
+ // flex container's `justify-center` actually centers the dialog
2267
+ // (without this, the dialog drops out of flex flow and `m-0`
2268
+ // kills the user-agent's `margin: auto` centering, pinning the
2269
+ // dialog to top-left).
2270
+ "static m-0 p-0 border-0 bg-transparent",
2271
+ // Pre-existing dialog frame
2272
+ "pointer-events-auto w-full flex flex-col bg-surface border shadow-elevation-dialog rounded-container",
2273
+ // Desktop sizing + viewport-aware floor.
2274
+ sizeClasses2[size],
2275
+ minWidthClasses[size],
2276
+ "max-h-[80vh]",
2277
+ // Mobile: take the entire screen. Override desktop max-w cap,
2278
+ // full height, no rounded corners, no min-width.
2279
+ "max-sm:max-w-none max-sm:max-h-none max-sm:w-full max-sm:h-full max-sm:rounded-none",
2280
+ lookStyles[look],
2281
+ className
2317
2282
  ),
2318
- (title || showCloseButton) && /* @__PURE__ */ jsxs(
2319
- Box,
2320
- {
2321
- className: cn(
2322
- "px-6 py-4 flex items-center justify-between",
2323
- "border-b-[length:var(--border-width)] border-border"
2324
- ),
2325
- children: [
2326
- title && /* @__PURE__ */ jsx(Typography, { variant: "h4", as: "h2", id: "modal-title", children: title }),
2327
- showCloseButton && /* @__PURE__ */ jsx(
2328
- Button,
2329
- {
2330
- variant: "ghost",
2331
- size: "sm",
2332
- icon: "x",
2333
- onClick: handleClose,
2334
- "data-event": "CLOSE",
2335
- "aria-label": "Close modal"
2283
+ style: dragY > 0 ? {
2284
+ transform: `translateY(${dragY}px)`,
2285
+ transition: isDragging.current ? "none" : "transform 200ms ease-out"
2286
+ } : void 0,
2287
+ ...title && { "aria-labelledby": "modal-title" },
2288
+ children: [
2289
+ /* @__PURE__ */ jsx(
2290
+ Box,
2291
+ {
2292
+ className: "hidden max-sm:flex justify-center py-2 cursor-grab active:cursor-grabbing touch-none",
2293
+ onPointerDown: (e) => {
2294
+ if (!swipeDownToClose) return;
2295
+ dragStartY.current = e.clientY;
2296
+ isDragging.current = true;
2297
+ e.target.setPointerCapture(e.pointerId);
2298
+ },
2299
+ onPointerMove: (e) => {
2300
+ if (!isDragging.current) return;
2301
+ const dy = Math.max(0, e.clientY - dragStartY.current);
2302
+ setDragY(dy);
2303
+ },
2304
+ onPointerUp: () => {
2305
+ if (!isDragging.current) return;
2306
+ isDragging.current = false;
2307
+ if (dragY > 100) {
2308
+ handleClose();
2336
2309
  }
2337
- )
2338
- ]
2339
- }
2340
- ),
2341
- /* @__PURE__ */ jsx(Box, { className: "flex-1 overflow-y-auto p-6", children }),
2342
- footer && /* @__PURE__ */ jsx(
2343
- Box,
2344
- {
2345
- className: cn(
2346
- "px-6 py-4 bg-muted",
2347
- "border-t-[length:var(--border-width)] border-border"
2348
- ),
2349
- children: footer
2350
- }
2351
- )
2352
- ]
2353
- }
2354
- )
2355
- }
2356
- )
2357
- ] });
2310
+ setDragY(0);
2311
+ },
2312
+ onPointerCancel: () => {
2313
+ isDragging.current = false;
2314
+ setDragY(0);
2315
+ },
2316
+ children: /* @__PURE__ */ jsx(Box, { className: "w-10 h-1 rounded-full bg-border" })
2317
+ }
2318
+ ),
2319
+ (title || showCloseButton) && /* @__PURE__ */ jsxs(
2320
+ Box,
2321
+ {
2322
+ className: cn(
2323
+ "px-6 py-4 flex items-center justify-between",
2324
+ "border-b-[length:var(--border-width)] border-border"
2325
+ ),
2326
+ children: [
2327
+ title && /* @__PURE__ */ jsx(Typography, { variant: "h4", as: "h2", id: "modal-title", children: title }),
2328
+ showCloseButton && /* @__PURE__ */ jsx(
2329
+ Button,
2330
+ {
2331
+ variant: "ghost",
2332
+ size: "sm",
2333
+ icon: "x",
2334
+ onClick: handleClose,
2335
+ "data-event": "CLOSE",
2336
+ "aria-label": "Close modal"
2337
+ }
2338
+ )
2339
+ ]
2340
+ }
2341
+ ),
2342
+ /* @__PURE__ */ jsx(Box, { className: "flex-1 overflow-y-auto p-6", children }),
2343
+ footer && /* @__PURE__ */ jsx(
2344
+ Box,
2345
+ {
2346
+ className: cn(
2347
+ "px-6 py-4 bg-muted",
2348
+ "border-t-[length:var(--border-width)] border-border"
2349
+ ),
2350
+ children: footer
2351
+ }
2352
+ )
2353
+ ]
2354
+ }
2355
+ )
2356
+ }
2357
+ )
2358
+ ] }),
2359
+ document.body
2360
+ );
2358
2361
  };
2359
2362
  Modal.displayName = "Modal";
2360
2363
  }
@@ -13815,7 +13818,7 @@ var init_CodeBlock = __esm({
13815
13818
  };
13816
13819
  };
13817
13820
  }, [errorLines]);
13818
- const isFoldable = foldableProp ?? (language === "orb" || language === "json");
13821
+ const isFoldable = foldableProp ?? true;
13819
13822
  const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
13820
13823
  const foldRegions = useMemo(
13821
13824
  () => isFoldable ? computeFoldRegions(code) : [],
@@ -13838,6 +13841,8 @@ var init_CodeBlock = __esm({
13838
13841
  collapsedRef.current = collapsed;
13839
13842
  const foldStartMapRef = useRef(foldStartMap);
13840
13843
  foldStartMapRef.current = foldStartMap;
13844
+ const hiddenLinesRef = useRef(hiddenLines);
13845
+ hiddenLinesRef.current = hiddenLines;
13841
13846
  const toggleFold = useCallback((lineNum) => {
13842
13847
  setCollapsed((prev) => {
13843
13848
  const next = new Set(prev);
@@ -13950,6 +13955,60 @@ var init_CodeBlock = __esm({
13950
13955
  eventBus.emit("UI:COPY_CODE", { language, success: false });
13951
13956
  }
13952
13957
  };
13958
+ const handleSelectionCopy = useCallback((e) => {
13959
+ if (hiddenLinesRef.current.size === 0) return;
13960
+ const sel = typeof window !== "undefined" ? window.getSelection() : null;
13961
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
13962
+ const lineOf = (node) => {
13963
+ const start = node instanceof HTMLElement ? node : node?.parentElement ?? null;
13964
+ const lineEl = start?.closest("[data-line]");
13965
+ if (!lineEl) return null;
13966
+ const n = parseInt(lineEl.getAttribute("data-line") ?? "", 10);
13967
+ return Number.isNaN(n) ? null : n;
13968
+ };
13969
+ const range = sel.getRangeAt(0);
13970
+ let a = lineOf(range.startContainer);
13971
+ let b = lineOf(range.endContainer);
13972
+ if (a === null || b === null) {
13973
+ const container = codeRef.current;
13974
+ if (!container) return;
13975
+ let min = Infinity, max = -Infinity;
13976
+ container.querySelectorAll("[data-line]").forEach((el) => {
13977
+ if (!sel.containsNode(el, true)) return;
13978
+ const n = parseInt(el.getAttribute("data-line") ?? "", 10);
13979
+ if (!Number.isNaN(n)) {
13980
+ min = Math.min(min, n);
13981
+ max = Math.max(max, n);
13982
+ }
13983
+ });
13984
+ if (min === Infinity) return;
13985
+ a = a ?? min;
13986
+ b = b ?? max;
13987
+ }
13988
+ if (a > b) [a, b] = [b, a];
13989
+ let touchesFold = false;
13990
+ for (let i = a; i <= b; i++) {
13991
+ if (hiddenLinesRef.current.has(i) || foldStartMapRef.current.has(i) && collapsedRef.current.has(i)) {
13992
+ touchesFold = true;
13993
+ break;
13994
+ }
13995
+ }
13996
+ if (!touchesFold) return;
13997
+ let endLine = b;
13998
+ let changed = true;
13999
+ while (changed) {
14000
+ changed = false;
14001
+ foldStartMapRef.current.forEach((region, start) => {
14002
+ if (start >= a && start <= endLine && collapsedRef.current.has(start) && region.end > endLine) {
14003
+ endLine = region.end;
14004
+ changed = true;
14005
+ }
14006
+ });
14007
+ }
14008
+ const full = code.split("\n").slice(a, endLine + 1).join("\n");
14009
+ e.clipboardData.setData("text/plain", full);
14010
+ e.preventDefault();
14011
+ }, [code]);
13953
14012
  const hasHeader = showLanguageBadge || showCopyButton;
13954
14013
  return /* @__PURE__ */ jsxs(Box, { className: `relative group ${className || ""}`, style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
13955
14014
  hasHeader && /* @__PURE__ */ jsxs(
@@ -14100,6 +14159,7 @@ var init_CodeBlock = __esm({
14100
14159
  "div",
14101
14160
  {
14102
14161
  ref: scrollRef,
14162
+ onCopy: handleSelectionCopy,
14103
14163
  style: {
14104
14164
  flex: 1,
14105
14165
  minHeight: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/ui",
3
- "version": "5.13.2",
3
+ "version": "5.14.0",
4
4
  "description": "React UI components, hooks, and providers for Almadar",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -169,7 +169,7 @@
169
169
  "three": "^0.160.0"
170
170
  },
171
171
  "devDependencies": {
172
- "@almadar/eslint-plugin": ">=2.3.0",
172
+ "@almadar/eslint-plugin": "^2.8.2",
173
173
  "@react-three/drei": "^10.7.7",
174
174
  "@react-three/fiber": "^9.6.0",
175
175
  "@react-three/postprocessing": "3.0.4",
@@ -242,6 +242,8 @@ module.exports = {
242
242
  'w-[480px]',
243
243
  'w-[640px]',
244
244
  'will-change-[opacity,transform]',
245
+ 'z-[1000]',
246
+ 'z-[1001]',
245
247
  'z-[60]',
246
248
  ],
247
249
  theme: {