@almadar/ui 5.155.0 → 5.157.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.
@@ -3,16 +3,16 @@ import * as React77 from 'react';
3
3
  import React77__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, useLayoutEffect, useId, Suspense, lazy, useSyncExternalStore } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
- import { EventBusContext, useTraitScopeChain, useCurrentPagePath, useGameAudioContextOptional, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider, useNavStack } from '@almadar/ui/providers';
6
+ import { EventBusContext, useTraitScopeChain, useCurrentPagePath, useGameAudioContextOptional, useRenderSlot, useNavStack, useEntitySchemaOptional, RenderSlotProvider, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider } from '@almadar/ui/providers';
7
7
  export { GameAudioContext, GameAudioProvider, useGameAudioContext, useNavStack } from '@almadar/ui/providers';
8
8
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
9
9
  import * as LucideIcons2 from 'lucide-react';
10
- import { X, List, Printer, ChevronRight, ChevronLeft, Check, Copy, RotateCcw, Play, Terminal, CheckCircle, XCircle, ChevronDown, Search, ChevronUp, MoreHorizontal, ArrowLeft, DollarSign, Package, Calendar, Pencil, Eye, Image as Image$1, Upload, ZoomIn, TrendingUp, TrendingDown, Minus, AlertCircle, Circle, Clock, CheckCircle2, Loader2, Code, FileText, WrapText, HelpCircle, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, GitBranch, Plus, ArrowRight, Trash, Menu as Menu$1, AlertTriangle, Trash2, Eraser, ZoomOut, Download, Lightbulb, PauseCircle, Link2, Tag, User } from 'lucide-react';
10
+ import { X, List, Printer, ChevronRight, ChevronLeft, Check, Copy, RotateCcw, Play, Terminal, CheckCircle, XCircle, ChevronDown, Search, ChevronUp, MoreHorizontal, ArrowLeft, DollarSign, Package, Calendar, Pencil, Eye, Image as Image$1, Upload, ZoomIn, TrendingUp, TrendingDown, Minus, AlertCircle, Circle, Clock, CheckCircle2, Loader2, Code, FileText, WrapText, HelpCircle, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, GitBranch, Plus, ArrowRight, Trash, Menu as Menu$1, AlertTriangle, Trash2, Eraser, ZoomOut, Download, Lightbulb, PauseCircle, Link2 } from 'lucide-react';
11
11
  import { useTranslate } from '@almadar/ui/hooks';
12
12
  import { useUISlots, useTheme } from '@almadar/ui/context';
13
13
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
14
14
  import { createContextFromBindings, interpolateValue } from '@almadar/runtime';
15
- import { mergeEntityFrame, isFileValue, isInlineTrait, isRenderBindingMarker, ANIMATION_NAMES } from '@almadar/core';
15
+ import { mergeEntityFrame, isFileValue, isInlineTrait, isSecretConfigType, isRenderBindingMarker, ANIMATION_NAMES } from '@almadar/core';
16
16
  import { createPortal } from 'react-dom';
17
17
  import { wrapCallbackForEvent } from '@almadar/runtime/ui';
18
18
  import { Link, Outlet, useLocation } from 'react-router-dom';
@@ -8421,6 +8421,11 @@ var init_ComponentPatterns = __esm({
8421
8421
  AlertPattern.displayName = "AlertPattern";
8422
8422
  }
8423
8423
  });
8424
+ function themeBodyFont(el) {
8425
+ if (typeof getComputedStyle !== "function") return "system-ui, sans-serif";
8426
+ const v = getComputedStyle(el).getPropertyValue("--font-family-body").trim();
8427
+ return v || "system-ui, sans-serif";
8428
+ }
8424
8429
  function resolveColor2(color, ctx, fallback) {
8425
8430
  if (!color) return fallback;
8426
8431
  if (color.startsWith("var(")) {
@@ -8609,6 +8614,8 @@ function drawShape(ctx, shape, width, height, allShapes) {
8609
8614
  case "path": {
8610
8615
  if (!shape.path) break;
8611
8616
  const p = new Path2D(shape.path);
8617
+ ctx.lineJoin = "round";
8618
+ ctx.lineCap = "round";
8612
8619
  if (fill) {
8613
8620
  ctx.fillStyle = fill;
8614
8621
  ctx.fill(p);
@@ -8620,7 +8627,7 @@ function drawShape(ctx, shape, width, height, allShapes) {
8620
8627
  case "text": {
8621
8628
  if (shape.x == null || shape.y == null || !shape.text) break;
8622
8629
  ctx.fillStyle = stroke;
8623
- ctx.font = `${shape.fontSize ?? 14}px system-ui, sans-serif`;
8630
+ ctx.font = `${shape.fontSize ?? 14}px ${themeBodyFont(ctx.canvas)}`;
8624
8631
  ctx.textAlign = shape.align ?? "left";
8625
8632
  ctx.textBaseline = "middle";
8626
8633
  ctx.fillText(shape.text, shape.x, shape.y);
@@ -8817,7 +8824,7 @@ var init_LearningCanvas = __esm({
8817
8824
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
8818
8825
  ctx.clearRect(0, 0, width, height);
8819
8826
  if (backgroundColor) {
8820
- ctx.fillStyle = backgroundColor;
8827
+ ctx.fillStyle = resolveColor2(backgroundColor, ctx, backgroundColor);
8821
8828
  ctx.fillRect(0, 0, width, height);
8822
8829
  }
8823
8830
  for (const shape of derivedShapes) {
@@ -16492,7 +16499,22 @@ function eventStartDate(event, startField) {
16492
16499
  const raw = getNestedValue(event, startField);
16493
16500
  return new Date(raw ?? "");
16494
16501
  }
16495
- function generateDefaultTimeSlots(events2, startField) {
16502
+ function eventEndDate(event, endField) {
16503
+ const raw = getNestedValue(event, endField);
16504
+ if (raw === void 0 || raw === null || raw === "") return null;
16505
+ const end = new Date(raw);
16506
+ return Number.isNaN(end.getTime()) ? null : end;
16507
+ }
16508
+ function eventContinuesInSlot(event, day, slotTime, startField, endField) {
16509
+ const eventStart = eventStartDate(event, startField);
16510
+ const eventEnd = eventEndDate(event, endField);
16511
+ if (eventEnd === null || Number.isNaN(eventStart.getTime())) return false;
16512
+ const [slotHour] = slotTime.split(":").map(Number);
16513
+ const slotStart = new Date(day);
16514
+ slotStart.setHours(slotHour, 0, 0, 0);
16515
+ return slotStart.getTime() > eventStart.getTime() && slotStart.getTime() < eventEnd.getTime();
16516
+ }
16517
+ function generateDefaultTimeSlots(events2, startField, endField) {
16496
16518
  let first = DEFAULT_FIRST_HOUR;
16497
16519
  let last = DEFAULT_LAST_HOUR;
16498
16520
  for (const ev of events2) {
@@ -16501,6 +16523,11 @@ function generateDefaultTimeSlots(events2, startField) {
16501
16523
  const hour = start.getHours();
16502
16524
  if (hour < first) first = hour;
16503
16525
  if (hour > last) last = hour;
16526
+ const end = eventEndDate(ev, endField);
16527
+ if (end && end.toDateString() === start.toDateString()) {
16528
+ const endHour = end.getMinutes() === 0 && end.getSeconds() === 0 ? end.getHours() - 1 : end.getHours();
16529
+ if (endHour > last) last = endHour;
16530
+ }
16504
16531
  }
16505
16532
  const slots = [];
16506
16533
  for (let hour = first; hour <= last; hour++) {
@@ -16529,6 +16556,7 @@ function CalendarGrid({
16529
16556
  dayWindow = "auto",
16530
16557
  titleField = "title",
16531
16558
  startField = "startTime",
16559
+ endField = "endTime",
16532
16560
  colorField = "color",
16533
16561
  children,
16534
16562
  renderItem
@@ -16546,8 +16574,8 @@ function CalendarGrid({
16546
16574
  [resolvedWeekStart]
16547
16575
  );
16548
16576
  const resolvedTimeSlots = useMemo(
16549
- () => timeSlots ?? generateDefaultTimeSlots(evs, startField),
16550
- [timeSlots, evs, startField]
16577
+ () => timeSlots ?? generateDefaultTimeSlots(evs, startField, endField),
16578
+ [timeSlots, evs, startField, endField]
16551
16579
  );
16552
16580
  const visibleCount = useDayWindow(dayWindow);
16553
16581
  const [dayOffset, setDayOffset] = useState(0);
@@ -16707,12 +16735,15 @@ function CalendarGrid({
16707
16735
  const slotEvents = evs.filter(
16708
16736
  (ev) => eventInSlot(ev, day, time, startField)
16709
16737
  );
16738
+ const continuingEvents = evs.filter(
16739
+ (ev) => eventContinuesInSlot(ev, day, time, startField, endField)
16740
+ );
16710
16741
  const isToday = day.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
16711
16742
  return /* @__PURE__ */ jsx(
16712
16743
  TimeSlotCell,
16713
16744
  {
16714
16745
  time,
16715
- isOccupied: slotEvents.length > 0,
16746
+ isOccupied: slotEvents.length > 0 || continuingEvents.length > 0,
16716
16747
  onClick: () => handleSlotClick(day, time),
16717
16748
  className: cn(
16718
16749
  "border-l border-border",
@@ -16723,7 +16754,25 @@ function CalendarGrid({
16723
16754
  onPointerUp: clearLongPress,
16724
16755
  onPointerCancel: clearLongPress
16725
16756
  } : {},
16726
- children: /* @__PURE__ */ jsx(VStack, { gap: "xs", children: slotEvents.map(renderEvent) })
16757
+ children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
16758
+ slotEvents.map(renderEvent),
16759
+ continuingEvents.map((event) => {
16760
+ const color = getNestedValue(event, colorField);
16761
+ return /* @__PURE__ */ jsx(
16762
+ Box,
16763
+ {
16764
+ rounded: "sm",
16765
+ border: true,
16766
+ className: cn(
16767
+ "cursor-pointer h-2",
16768
+ color ? cn(color, "opacity-50") : "bg-primary/10 border-primary/20"
16769
+ ),
16770
+ onClick: (e) => handleEventClick(event, e)
16771
+ },
16772
+ `${event.id}-cont`
16773
+ );
16774
+ })
16775
+ ] })
16727
16776
  },
16728
16777
  `${day.toISOString()}-${time}`
16729
16778
  );
@@ -21930,10 +21979,21 @@ function ControlGrid({
21930
21979
  directionAssets,
21931
21980
  size = "md",
21932
21981
  disabled,
21982
+ visibility = "auto",
21933
21983
  className
21934
21984
  }) {
21935
21985
  const eventBus = useEventBus();
21936
21986
  const [active, setActive] = React77.useState(/* @__PURE__ */ new Set());
21987
+ const [coarse, setCoarse] = React77.useState(
21988
+ () => typeof window !== "undefined" && window.matchMedia("(pointer: coarse)").matches
21989
+ );
21990
+ React77.useEffect(() => {
21991
+ if (visibility !== "auto" || typeof window === "undefined") return;
21992
+ const mq = window.matchMedia("(pointer: coarse)");
21993
+ const onChange = (e) => setCoarse(e.matches);
21994
+ mq.addEventListener("change", onChange);
21995
+ return () => mq.removeEventListener("change", onChange);
21996
+ }, [visibility]);
21937
21997
  const handlePress = React77.useCallback(
21938
21998
  (id) => {
21939
21999
  setActive((prev) => new Set(prev).add(id));
@@ -21966,6 +22026,7 @@ function ControlGrid({
21966
22026
  },
21967
22027
  [kind, actionEvent, directionEvent, directionReleaseEvents, eventBus, onAction, onDirection]
21968
22028
  );
22029
+ if (visibility === "auto" && !coarse) return null;
21969
22030
  if (kind === "dpad") {
21970
22031
  const ds = dpadSizeMap[size];
21971
22032
  const dir = (d) => /* @__PURE__ */ jsx(
@@ -25376,6 +25437,7 @@ function currentValue(decl, override) {
25376
25437
  function TextLikeControl({
25377
25438
  field,
25378
25439
  numeric,
25440
+ secret,
25379
25441
  value,
25380
25442
  onCommit
25381
25443
  }) {
@@ -25393,7 +25455,7 @@ function TextLikeControl({
25393
25455
  return /* @__PURE__ */ jsx(
25394
25456
  Input,
25395
25457
  {
25396
- inputType: numeric ? "number" : "text",
25458
+ inputType: secret ? "password" : numeric ? "number" : "text",
25397
25459
  value: draft,
25398
25460
  onChange: (e) => setDraft(e.target.value),
25399
25461
  onBlur: commit,
@@ -25454,6 +25516,8 @@ function FieldControl({
25454
25516
  );
25455
25517
  } else if (decl.type === "number") {
25456
25518
  control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: true, value, onCommit: onChange });
25519
+ } else if (isSecretConfigType(decl.type)) {
25520
+ control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: false, secret: true, value, onCommit: onChange });
25457
25521
  } else if (decl.type === "string") {
25458
25522
  control = /* @__PURE__ */ jsx(TextLikeControl, { field: name, numeric: false, value, onCommit: onChange });
25459
25523
  } else if (decl.type === "node") {
@@ -25493,7 +25557,7 @@ var init_PropertyInspector = __esm({
25493
25557
  init_JsonTreeEditor();
25494
25558
  init_NodeSlotEditor();
25495
25559
  TIER_ORDER = ["presentation", "domain", "policy", "infra", "internal"];
25496
- SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset", "Asset"]);
25560
+ SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "icon", "asset", "Asset", "secret"]);
25497
25561
  PropertyInspector = ({
25498
25562
  config,
25499
25563
  values,
@@ -29481,7 +29545,7 @@ function StatBadge({
29481
29545
  assetUrl,
29482
29546
  iconUrl,
29483
29547
  label,
29484
- value = 0,
29548
+ value,
29485
29549
  max,
29486
29550
  format = "number",
29487
29551
  icon,
@@ -29492,6 +29556,7 @@ function StatBadge({
29492
29556
  source: _source,
29493
29557
  field: _field
29494
29558
  }) {
29559
+ const hasValue = value !== void 0 && value !== null;
29495
29560
  const numValue = typeof value === "number" ? value : parseInt(String(value), 10) || 0;
29496
29561
  const resolvedAsset = iconUrl ?? assetUrl;
29497
29562
  return /* @__PURE__ */ jsxs(
@@ -29505,8 +29570,8 @@ function StatBadge({
29505
29570
  ),
29506
29571
  children: [
29507
29572
  resolvedAsset ? /* @__PURE__ */ jsx(GameIcon, { assetUrl: resolvedAsset, icon: "image", size: 16, className: "flex-shrink-0" }) : icon ? /* @__PURE__ */ jsx(Box, { as: "span", className: "flex-shrink-0 text-lg", children: typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, className: "w-4 h-4" }) : /* @__PURE__ */ jsx(Icon, { icon, className: "w-4 h-4" }) }) : null,
29508
- /* @__PURE__ */ jsx(Typography, { as: "span", className: "text-muted-foreground font-medium", children: label }),
29509
- format === "hearts" && max && /* @__PURE__ */ jsx(
29573
+ /* @__PURE__ */ jsx(Typography, { as: "span", className: "text-muted-foreground font-medium text-xs", children: label }),
29574
+ hasValue && format === "hearts" && max && /* @__PURE__ */ jsx(
29510
29575
  HealthBar,
29511
29576
  {
29512
29577
  current: numValue,
@@ -29515,7 +29580,7 @@ function StatBadge({
29515
29580
  size: size === "lg" ? "md" : "sm"
29516
29581
  }
29517
29582
  ),
29518
- format === "bar" && max && /* @__PURE__ */ jsx(
29583
+ hasValue && format === "bar" && max && /* @__PURE__ */ jsx(
29519
29584
  HealthBar,
29520
29585
  {
29521
29586
  current: numValue,
@@ -29524,14 +29589,15 @@ function StatBadge({
29524
29589
  size: size === "lg" ? "md" : "sm"
29525
29590
  }
29526
29591
  ),
29527
- format === "number" && /* @__PURE__ */ jsx(
29592
+ hasValue && format === "number" && /* @__PURE__ */ jsx(
29528
29593
  ScoreDisplay,
29529
29594
  {
29530
29595
  value: numValue,
29531
- size: size === "lg" ? "md" : "sm"
29596
+ size: size === "lg" ? "md" : "sm",
29597
+ className: "font-display"
29532
29598
  }
29533
29599
  ),
29534
- format === "text" && /* @__PURE__ */ jsx(Typography, { as: "span", className: "font-bold text-foreground", children: value })
29600
+ hasValue && format === "text" && /* @__PURE__ */ jsx(Typography, { as: "span", className: "font-bold text-foreground", children: value })
29535
29601
  ]
29536
29602
  }
29537
29603
  );
@@ -29547,6 +29613,7 @@ var init_StatBadge = __esm({
29547
29613
  init_HealthBar();
29548
29614
  init_ScoreDisplay();
29549
29615
  sizeMap7 = {
29616
+ xs: "text-xs px-1.5 py-0.5",
29550
29617
  sm: "text-xs px-2 py-1",
29551
29618
  md: "text-sm px-3 py-1.5",
29552
29619
  lg: "text-base px-4 py-2"
@@ -29589,6 +29656,7 @@ function GameHud({
29589
29656
  items,
29590
29657
  elements,
29591
29658
  size = "md",
29659
+ variant = "floating",
29592
29660
  className,
29593
29661
  transparent = true
29594
29662
  }) {
@@ -29603,7 +29671,7 @@ function GameHud({
29603
29671
  /* @__PURE__ */ jsx(Box, { position: "absolute", className: "top-4 right-4 flex flex-col gap-2 items-end pointer-events-auto", children: rightStats.map((stat, i) => /* @__PURE__ */ jsx(StatBadge, { ...stat, size }, i)) })
29604
29672
  ] });
29605
29673
  }
29606
- if (position === "top" || position === "bottom") {
29674
+ if ((position === "top" || position === "bottom") && variant === "bar") {
29607
29675
  const mid = Math.ceil(stats.length / 2);
29608
29676
  const leftStats = stats.slice(0, mid);
29609
29677
  const rightStats = stats.slice(mid);
@@ -31260,7 +31328,7 @@ var init_physicsPresets = __esm({
31260
31328
  ];
31261
31329
  }
31262
31330
  });
31263
- var FONT_BASE, GAME_FONTS, FONT_FACES, GameShell;
31331
+ var GAME_FONTS, GameShell;
31264
31332
  var init_GameShell = __esm({
31265
31333
  "components/game/templates/GameShell.tsx"() {
31266
31334
  init_cn();
@@ -31268,7 +31336,6 @@ var init_GameShell = __esm({
31268
31336
  init_Card();
31269
31337
  init_Typography();
31270
31338
  init_AtlasImage();
31271
- FONT_BASE = "https://almadar-kflow-assets.web.app/shared/_shared/kenney-fonts/fonts";
31272
31339
  GAME_FONTS = {
31273
31340
  future: "Kenney Future",
31274
31341
  "future-narrow": "Kenney Future Narrow",
@@ -31276,14 +31343,6 @@ var init_GameShell = __esm({
31276
31343
  blocks: "Kenney Blocks",
31277
31344
  mini: "Kenney Mini"
31278
31345
  };
31279
- FONT_FACES = `
31280
- @font-face { font-family: 'Kenney Future'; src: url('${FONT_BASE}/Kenney%20Future.ttf') format('truetype'); font-display: swap; }
31281
- @font-face { font-family: 'Kenney Future Narrow'; src: url('${FONT_BASE}/Kenney%20Future%20Narrow.ttf') format('truetype'); font-display: swap; }
31282
- @font-face { font-family: 'Kenney Pixel'; src: url('${FONT_BASE}/Kenney%20Pixel.ttf') format('truetype'); font-display: swap; }
31283
- @font-face { font-family: 'Kenney Blocks'; src: url('${FONT_BASE}/Kenney%20Blocks.ttf') format('truetype'); font-display: swap; }
31284
- @font-face { font-family: 'Kenney Mini'; src: url('${FONT_BASE}/Kenney%20Mini.ttf') format('truetype'); font-display: swap; }
31285
- .game-shell, .game-shell * { font-family: inherit; }
31286
- `;
31287
31346
  GameShell = ({
31288
31347
  appName = "Game",
31289
31348
  hud,
@@ -31310,10 +31369,12 @@ var init_GameShell = __esm({
31310
31369
  overflow: "hidden",
31311
31370
  background: "var(--color-background, #0a0a0f)",
31312
31371
  color: "var(--color-foreground, #e0e0e0)",
31313
- fontFamily: `'${font}', system-ui, sans-serif`
31372
+ // The fontFamily knob is a scoped override of the theme contract's
31373
+ // display slot: titles/numerics take the game face, body text keeps
31374
+ // the active theme's --font-family-body.
31375
+ "--font-family-display": `'${font}', ui-sans-serif, system-ui, sans-serif`
31314
31376
  },
31315
31377
  children: [
31316
- /* @__PURE__ */ jsx("style", { children: FONT_FACES }),
31317
31378
  backgroundAsset && /* @__PURE__ */ jsx(
31318
31379
  AtlasPanel,
31319
31380
  {
@@ -31351,6 +31412,7 @@ var init_GameShell = __esm({
31351
31412
  Typography,
31352
31413
  {
31353
31414
  as: "span",
31415
+ className: "font-display",
31354
31416
  style: {
31355
31417
  fontWeight: 700,
31356
31418
  fontSize: "1.05rem",
@@ -31483,6 +31545,9 @@ var init_MathCanvas = __esm({
31483
31545
  showAxes = true,
31484
31546
  showGrid = true,
31485
31547
  gridStep = 1,
31548
+ backgroundColor,
31549
+ gridColor = "var(--color-border, #9ca3af)",
31550
+ axisColor = "var(--color-muted-foreground, #374151)",
31486
31551
  showTickLabels = false,
31487
31552
  showCurveLabels = false,
31488
31553
  curves = [],
@@ -31537,11 +31602,11 @@ var init_MathCanvas = __esm({
31537
31602
  if (showGrid) {
31538
31603
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep) {
31539
31604
  const px = mapX(x);
31540
- out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
31605
+ out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color: gridColor, opacity: 0.35, lineWidth: 1 });
31541
31606
  }
31542
31607
  for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep) {
31543
31608
  const py = mapY(y);
31544
- out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: "#9ca3af", opacity: 0.35, lineWidth: 1 });
31609
+ out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color: gridColor, opacity: 0.35, lineWidth: 1 });
31545
31610
  }
31546
31611
  }
31547
31612
  if (showTickLabels) {
@@ -31612,8 +31677,8 @@ var init_MathCanvas = __esm({
31612
31677
  });
31613
31678
  }
31614
31679
  if (showAxes) {
31615
- out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: "#374151", lineWidth: 2 });
31616
- out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: "#374151", lineWidth: 2 });
31680
+ out.push({ type: "line", x1: margin, y1: xAxisY, x2: width - margin, y2: xAxisY, color: axisColor, lineWidth: 2 });
31681
+ out.push({ type: "line", x1: yAxisX, y1: margin, x2: yAxisX, y2: height - margin, color: axisColor, lineWidth: 2 });
31617
31682
  }
31618
31683
  for (const guide of guides) {
31619
31684
  const color = guide.color ?? "#9ca3af";
@@ -31636,23 +31701,36 @@ var init_MathCanvas = __esm({
31636
31701
  }
31637
31702
  for (const curve of curves) {
31638
31703
  if (!curve.samples || curve.samples.length < 2) continue;
31704
+ let d = "";
31705
+ let penDown = false;
31639
31706
  let lastInRange;
31640
31707
  for (let i = 1; i < curve.samples.length; i++) {
31641
31708
  const a = curve.samples[i - 1];
31642
31709
  const b = curve.samples[i];
31643
- if (a.x < xMin || a.x > xMax || b.x < xMin || b.x > xMax) continue;
31644
- out.push({
31645
- type: "line",
31646
- x1: mapX(a.x),
31647
- y1: mapY(a.y),
31648
- x2: mapX(b.x),
31649
- y2: mapY(b.y),
31650
- color: curve.color ?? "#2563eb",
31651
- lineWidth: 2,
31652
- dash: curve.dash
31653
- });
31654
- lastInRange = b;
31655
- }
31710
+ if (a.x < xMin && b.x < xMin || a.x > xMax && b.x > xMax) {
31711
+ penDown = false;
31712
+ continue;
31713
+ }
31714
+ const clip = (p, q, xLim) => {
31715
+ const t = q.x === p.x ? 0 : (xLim - p.x) / (q.x - p.x);
31716
+ return { x: xLim, y: p.y + t * (q.y - p.y) };
31717
+ };
31718
+ const ca = a.x < xMin ? clip(a, b, xMin) : a.x > xMax ? clip(a, b, xMax) : a;
31719
+ const cb = b.x < xMin ? clip(a, b, xMin) : b.x > xMax ? clip(a, b, xMax) : b;
31720
+ const pax = mapX(ca.x);
31721
+ const pay = mapY(ca.y);
31722
+ d += `${penDown ? "L" : "M"} ${pax} ${pay} L ${mapX(cb.x)} ${mapY(cb.y)} `;
31723
+ penDown = true;
31724
+ if (b.x >= xMin && b.x <= xMax) lastInRange = b;
31725
+ }
31726
+ if (!d) continue;
31727
+ out.push({
31728
+ type: "path",
31729
+ path: d,
31730
+ color: curve.color ?? "#2563eb",
31731
+ lineWidth: 2,
31732
+ dash: curve.dash
31733
+ });
31656
31734
  if (showCurveLabels && curve.label && lastInRange) {
31657
31735
  out.push({
31658
31736
  type: "text",
@@ -31767,6 +31845,8 @@ var init_MathCanvas = __esm({
31767
31845
  showAxes,
31768
31846
  showGrid,
31769
31847
  gridStep,
31848
+ gridColor,
31849
+ axisColor,
31770
31850
  showTickLabels,
31771
31851
  showCurveLabels,
31772
31852
  curves,
@@ -31786,6 +31866,7 @@ var init_MathCanvas = __esm({
31786
31866
  {
31787
31867
  width,
31788
31868
  height,
31869
+ backgroundColor,
31789
31870
  shapes: derivedShapes,
31790
31871
  readouts,
31791
31872
  traces,
@@ -33016,13 +33097,13 @@ var init_MapView = __esm({
33016
33097
  shadowSize: [41, 41]
33017
33098
  });
33018
33099
  L.Marker.prototype.options.icon = defaultIcon;
33019
- const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback108, useState: useState104 } = React77__default;
33100
+ const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback108, useState: useState104 } = React77__default;
33020
33101
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
33021
33102
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
33022
33103
  function MapUpdater({ centerLat, centerLng, zoom }) {
33023
33104
  const map = useMap();
33024
33105
  const prevRef = useRef65({ centerLat, centerLng, zoom });
33025
- useEffect67(() => {
33106
+ useEffect69(() => {
33026
33107
  const prev = prevRef.current;
33027
33108
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
33028
33109
  map.setView([centerLat, centerLng], zoom);
@@ -33033,7 +33114,7 @@ var init_MapView = __esm({
33033
33114
  }
33034
33115
  function MapClickHandler({ onMapClick }) {
33035
33116
  const map = useMap();
33036
- useEffect67(() => {
33117
+ useEffect69(() => {
33037
33118
  if (!onMapClick) return;
33038
33119
  const handler = (e) => {
33039
33120
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -33562,14 +33643,34 @@ var init_UploadDropZone = __esm({
33562
33643
  if (valid.length > 0) {
33563
33644
  onFiles?.(valid);
33564
33645
  if (action) {
33565
- eventBus.emit(`UI:${action}`, {
33566
- ...actionPayload,
33567
- files: valid.map((f3) => ({ name: f3.name, size: f3.size, type: f3.type }))
33646
+ void Promise.all(
33647
+ valid.map(
33648
+ (f3) => new Promise(
33649
+ (resolvePayload, rejectPayload) => {
33650
+ const reader = new FileReader();
33651
+ reader.onload = () => resolvePayload({
33652
+ name: f3.name,
33653
+ size: f3.size,
33654
+ type: f3.type,
33655
+ content: String(reader.result ?? "")
33656
+ });
33657
+ reader.onerror = () => rejectPayload(reader.error);
33658
+ reader.readAsDataURL(f3);
33659
+ }
33660
+ )
33661
+ )
33662
+ ).then((payloadFiles) => {
33663
+ eventBus.emit(`UI:${action}`, {
33664
+ ...actionPayload,
33665
+ files: payloadFiles
33666
+ });
33667
+ }).catch(() => {
33668
+ setError(t("Could not read the selected file"));
33568
33669
  });
33569
33670
  }
33570
33671
  }
33571
33672
  },
33572
- [validateFiles, onFiles, action, actionPayload, eventBus]
33673
+ [validateFiles, onFiles, action, actionPayload, eventBus, t]
33573
33674
  );
33574
33675
  const handleDragOver = (e) => {
33575
33676
  e.preventDefault();
@@ -43311,22 +43412,6 @@ var init_DataTable = __esm({
43311
43412
  DataTable.displayName = "DataTable";
43312
43413
  }
43313
43414
  });
43314
- function getFieldIcon(fieldName) {
43315
- const name = fieldName.toLowerCase();
43316
- if (name.includes("date") || name.includes("time")) return Calendar;
43317
- if (name.includes("status")) return Tag;
43318
- if (name.includes("priority")) return AlertCircle;
43319
- if (name.includes("progress") || name.includes("percent")) return TrendingUp;
43320
- if (name.includes("assignee") || name.includes("owner") || name.includes("user") || name.includes("member"))
43321
- return User;
43322
- if (name.includes("due")) return Clock;
43323
- if (name.includes("complete")) return CheckCircle2;
43324
- if (name.includes("budget") || name.includes("cost") || name.includes("price"))
43325
- return DollarSign;
43326
- if (name.includes("description") || name.includes("note") || name.includes("comment"))
43327
- return FileText;
43328
- return Package;
43329
- }
43330
43415
  function getBadgeVariant(fieldName, value) {
43331
43416
  const name = fieldName.toLowerCase();
43332
43417
  const val = String(value).toLowerCase();
@@ -43376,6 +43461,18 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
43376
43461
  }
43377
43462
  ) });
43378
43463
  }
43464
+ if (fieldType === "url" && /^https?:\/\//i.test(str2)) {
43465
+ return /* @__PURE__ */ jsx(
43466
+ "a",
43467
+ {
43468
+ href: str2,
43469
+ target: "_blank",
43470
+ rel: "noreferrer",
43471
+ className: "text-primary hover:underline break-all",
43472
+ children: str2
43473
+ }
43474
+ );
43475
+ }
43379
43476
  return str2;
43380
43477
  }
43381
43478
  case "markdown":
@@ -43474,6 +43571,23 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
43474
43571
  }
43475
43572
  return str2;
43476
43573
  }
43574
+ case "boolean": {
43575
+ if (typeof value === "boolean") return value ? "Yes" : "No";
43576
+ if (str2 === "true") return "Yes";
43577
+ if (str2 === "false") return "No";
43578
+ return str2;
43579
+ }
43580
+ case "array": {
43581
+ if (Array.isArray(value) && value.length > 0) {
43582
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", wrap: true, children: value.map((item, i) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: String(item) }, i)) });
43583
+ }
43584
+ if (Array.isArray(value)) return "\u2014";
43585
+ return str2;
43586
+ }
43587
+ case "email":
43588
+ return /* @__PURE__ */ jsx("a", { href: `mailto:${str2}`, className: "text-primary hover:underline break-all", children: str2 });
43589
+ case "phone":
43590
+ return /* @__PURE__ */ jsx("a", { href: `tel:${str2}`, className: "text-primary hover:underline", children: str2 });
43477
43591
  default:
43478
43592
  if (meta?.values && meta.values.length > 0 && meta.values.includes(str2)) {
43479
43593
  return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName, str2), children: humanizeEnumValue(str2) });
@@ -43546,10 +43660,11 @@ var init_DetailPanel = __esm({
43546
43660
  avatar,
43547
43661
  sections: propSections,
43548
43662
  actions,
43549
- maxInlineActions,
43663
+ maxInlineActions = 2,
43550
43664
  backAction,
43551
43665
  footer,
43552
43666
  slideOver = false,
43667
+ showActions = true,
43553
43668
  className,
43554
43669
  entity,
43555
43670
  fields: propFields,
@@ -43597,9 +43712,6 @@ var init_DetailPanel = __esm({
43597
43712
  },
43598
43713
  [eventBus]
43599
43714
  );
43600
- const handleClose = useCallback(() => {
43601
- eventBus.emit("UI:CLOSE", {});
43602
- }, [eventBus]);
43603
43715
  const entityRecord = Array.isArray(entity) ? entity[0] : entity;
43604
43716
  const data = entityRecord ?? initialData;
43605
43717
  let title = propTitle;
@@ -43613,8 +43725,7 @@ var init_DetailPanel = __esm({
43613
43725
  const value = getNestedValue(normalizedData, field);
43614
43726
  return {
43615
43727
  label: labelFor(field),
43616
- value: formatFieldValue2(value, field),
43617
- icon: getFieldIcon(field)
43728
+ value: formatFieldValue2(value, field)
43618
43729
  };
43619
43730
  }
43620
43731
  return field;
@@ -43648,15 +43759,14 @@ var init_DetailPanel = __esm({
43648
43759
  (f3) => (!titleDerivedFromPrimary || f3 !== primaryField) && !statusFields.includes(f3) && !progressFields.includes(f3) && !metricFields.includes(f3) && !dateFields.includes(f3) && !descriptionFields.includes(f3)
43649
43760
  );
43650
43761
  sections = [];
43651
- if (statusFields.length > 0 || otherFields.length > 0) {
43762
+ if (otherFields.length > 0) {
43652
43763
  const overviewFields = [];
43653
- [...statusFields, ...otherFields].forEach((field) => {
43764
+ otherFields.forEach((field) => {
43654
43765
  const value = getNestedValue(normalizedData, field);
43655
43766
  if (value !== void 0 && value !== null) {
43656
43767
  overviewFields.push({
43657
43768
  label: labelFor(field),
43658
- value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field)),
43659
- icon: getFieldIcon(field)
43769
+ value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field))
43660
43770
  });
43661
43771
  }
43662
43772
  });
@@ -43671,8 +43781,7 @@ var init_DetailPanel = __esm({
43671
43781
  if (value !== void 0 && value !== null) {
43672
43782
  metricsFields.push({
43673
43783
  label: labelFor(field),
43674
- value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field)),
43675
- icon: getFieldIcon(field)
43784
+ value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field))
43676
43785
  });
43677
43786
  }
43678
43787
  });
@@ -43687,8 +43796,7 @@ var init_DetailPanel = __esm({
43687
43796
  if (value !== void 0 && value !== null) {
43688
43797
  timelineFields.push({
43689
43798
  label: labelFor(field),
43690
- value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field)),
43691
- icon: getFieldIcon(field)
43799
+ value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field))
43692
43800
  });
43693
43801
  }
43694
43802
  });
@@ -43703,8 +43811,7 @@ var init_DetailPanel = __esm({
43703
43811
  if (value !== void 0 && value !== null) {
43704
43812
  descFields.push({
43705
43813
  label: labelFor(field),
43706
- value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field)),
43707
- icon: getFieldIcon(field)
43814
+ value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field))
43708
43815
  });
43709
43816
  }
43710
43817
  });
@@ -43713,6 +43820,15 @@ var init_DetailPanel = __esm({
43713
43820
  }
43714
43821
  }
43715
43822
  }
43823
+ const renderSlot = useRenderSlot();
43824
+ const navStack = useNavStack();
43825
+ const { setCurrentLabel } = navStack;
43826
+ const resolvedTitle = normalizedData ? title : void 0;
43827
+ useEffect(() => {
43828
+ if (renderSlot === "main" && !slideOver && resolvedTitle) {
43829
+ setCurrentLabel(resolvedTitle);
43830
+ }
43831
+ }, [renderSlot, slideOver, resolvedTitle, setCurrentLabel]);
43716
43832
  if (isLoading) {
43717
43833
  return /* @__PURE__ */ jsx(
43718
43834
  LoadingState,
@@ -43751,8 +43867,7 @@ var init_DetailPanel = __esm({
43751
43867
  const value = normalizedData ? getNestedValue(normalizedData, field) : void 0;
43752
43868
  allFields.push({
43753
43869
  label: labelFor(field),
43754
- value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field)),
43755
- icon: getFieldIcon(field)
43870
+ value: renderRichFieldValue(value, field, fieldTypeMap[field], metaFor(field))
43756
43871
  });
43757
43872
  } else {
43758
43873
  allFields.push(field);
@@ -43764,24 +43879,50 @@ var init_DetailPanel = __esm({
43764
43879
  (a) => a.event === "CLOSE" || a.event === "CANCEL" || a.label?.toLowerCase() === "close"
43765
43880
  );
43766
43881
  const otherActions = actions?.filter((a) => a !== closeAction) ?? [];
43767
- const effectiveCloseAction = closeAction ?? { event: void 0};
43768
- const content = /* @__PURE__ */ jsx(Card, { variant: "elevated", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", className: "p-6", children: [
43769
- /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "center", gap: "xs", children: [
43770
- /* @__PURE__ */ jsx(HStack, { align: "center", gap: "xs", children: backAction && /* @__PURE__ */ jsx(
43771
- Button,
43882
+ const statusBadges = /* @__PURE__ */ jsxs(Fragment, { children: [
43883
+ normalizedData && effectiveFieldNames && effectiveFieldNames.filter(
43884
+ (f3) => f3.toLowerCase().includes("status") || f3.toLowerCase().includes("priority")
43885
+ ).map((field) => {
43886
+ const value = getNestedValue(normalizedData, field);
43887
+ if (!value) return null;
43888
+ return /* @__PURE__ */ jsx(
43889
+ Badge,
43772
43890
  {
43773
- variant: backAction.variant || "ghost",
43774
- size: "sm",
43775
- action: backAction.navigatesTo ? void 0 : backAction.event,
43776
- actionPayload: { row: normalizedData },
43777
- onClick: backAction.navigatesTo ? () => handleActionClick(backAction, normalizedData) : void 0,
43778
- icon: backAction.icon ?? ArrowLeft,
43779
- "data-testid": backAction.event ? `action-${backAction.event}` : "action-back",
43780
- children: backAction.label
43781
- }
43782
- ) }),
43783
- /* @__PURE__ */ jsxs(HStack, { justify: "end", align: "center", gap: "xs", children: [
43784
- (maxInlineActions != null ? otherActions.slice(0, maxInlineActions) : otherActions).map((action, idx) => /* @__PURE__ */ jsx(
43891
+ variant: getBadgeVariant(field, String(value)),
43892
+ children: humanizeEnumValue(String(value))
43893
+ },
43894
+ field
43895
+ );
43896
+ }),
43897
+ status && /* @__PURE__ */ jsx(Badge, { variant: status.variant ?? "default", children: status.label })
43898
+ ] });
43899
+ const content = /* @__PURE__ */ jsx(Card, { variant: "elevated", children: /* @__PURE__ */ jsxs(VStack, { gap: "md", className: "p-6", children: [
43900
+ /* @__PURE__ */ jsxs(HStack, { justify: "between", align: "start", gap: "md", children: [
43901
+ /* @__PURE__ */ jsxs(HStack, { align: "start", gap: "sm", className: "min-w-0", children: [
43902
+ backAction && /* @__PURE__ */ jsx(
43903
+ Button,
43904
+ {
43905
+ variant: backAction.variant || "ghost",
43906
+ size: "sm",
43907
+ action: backAction.navigatesTo ? void 0 : backAction.event,
43908
+ actionPayload: { row: normalizedData },
43909
+ onClick: backAction.navigatesTo ? () => handleActionClick(backAction, normalizedData) : void 0,
43910
+ icon: backAction.icon ?? ArrowLeft,
43911
+ "data-testid": backAction.event ? `action-${backAction.event}` : "action-back",
43912
+ children: backAction.label
43913
+ }
43914
+ ),
43915
+ avatar,
43916
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "min-w-0", children: [
43917
+ /* @__PURE__ */ jsxs(HStack, { align: "center", gap: "sm", wrap: true, children: [
43918
+ /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" }),
43919
+ statusBadges
43920
+ ] }),
43921
+ subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: subtitle })
43922
+ ] })
43923
+ ] }),
43924
+ showActions && /* @__PURE__ */ jsxs(HStack, { justify: "end", align: "center", gap: "xs", className: "shrink-0", children: [
43925
+ otherActions.slice(0, maxInlineActions).map((action, idx) => /* @__PURE__ */ jsx(
43785
43926
  Button,
43786
43927
  {
43787
43928
  variant: action.variant || "secondary",
@@ -43796,7 +43937,7 @@ var init_DetailPanel = __esm({
43796
43937
  },
43797
43938
  idx
43798
43939
  )),
43799
- maxInlineActions != null && otherActions.length > maxInlineActions && /* @__PURE__ */ jsx(
43940
+ otherActions.length > maxInlineActions && /* @__PURE__ */ jsx(
43800
43941
  Menu,
43801
43942
  {
43802
43943
  position: "bottom-end",
@@ -43812,40 +43953,20 @@ var init_DetailPanel = __esm({
43812
43953
  }))
43813
43954
  }
43814
43955
  ),
43815
- /* @__PURE__ */ jsx(
43956
+ closeAction && /* @__PURE__ */ jsx(
43816
43957
  Button,
43817
43958
  {
43818
43959
  variant: "ghost",
43819
43960
  size: "sm",
43820
- action: effectiveCloseAction.event,
43961
+ action: closeAction.event,
43821
43962
  actionPayload: { row: normalizedData },
43822
- onClick: effectiveCloseAction.event ? void 0 : handleClose,
43963
+ onClick: closeAction.event ? void 0 : () => handleActionClick(closeAction, normalizedData),
43823
43964
  icon: X,
43824
- "data-testid": effectiveCloseAction.event ? `action-${effectiveCloseAction.event}` : "action-close"
43965
+ "data-testid": closeAction.event ? `action-${closeAction.event}` : "action-close"
43825
43966
  }
43826
43967
  )
43827
43968
  ] })
43828
43969
  ] }),
43829
- avatar,
43830
- /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || "Details" }),
43831
- subtitle && /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: subtitle }),
43832
- /* @__PURE__ */ jsxs(HStack, { gap: "xs", wrap: true, children: [
43833
- normalizedData && effectiveFieldNames && effectiveFieldNames.filter(
43834
- (f3) => f3.toLowerCase().includes("status") || f3.toLowerCase().includes("priority")
43835
- ).map((field) => {
43836
- const value = getNestedValue(normalizedData, field);
43837
- if (!value) return null;
43838
- return /* @__PURE__ */ jsx(
43839
- Badge,
43840
- {
43841
- variant: getBadgeVariant(field, String(value)),
43842
- children: String(value)
43843
- },
43844
- field
43845
- );
43846
- }),
43847
- status && /* @__PURE__ */ jsx(Badge, { variant: status.variant ?? "default", children: status.label })
43848
- ] }),
43849
43970
  normalizedData && effectiveFieldNames && effectiveFieldNames.filter(
43850
43971
  (f3) => f3.toLowerCase().includes("progress") || f3.toLowerCase().includes("percent")
43851
43972
  ).map((field) => {
@@ -43878,9 +43999,10 @@ var init_DetailPanel = __esm({
43878
43999
  /* @__PURE__ */ jsx(
43879
44000
  Typography,
43880
44001
  {
43881
- variant: "small",
43882
- color: "secondary",
44002
+ variant: "caption",
44003
+ color: "muted",
43883
44004
  weight: "medium",
44005
+ className: "uppercase tracking-wider",
43884
44006
  children: field.label
43885
44007
  }
43886
44008
  ),
@@ -43949,7 +44071,7 @@ var init_DrawerSlot = __esm({
43949
44071
  position,
43950
44072
  width: size,
43951
44073
  className,
43952
- children
44074
+ children: /* @__PURE__ */ jsx(RenderSlotProvider, { slot: "drawer", children })
43953
44075
  }
43954
44076
  );
43955
44077
  };
@@ -45996,7 +46118,7 @@ var init_ModalSlot = __esm({
45996
46118
  title,
45997
46119
  size,
45998
46120
  className,
45999
- children
46121
+ children: /* @__PURE__ */ jsx(RenderSlotProvider, { slot: "modal", children })
46000
46122
  }
46001
46123
  );
46002
46124
  };
@@ -50399,7 +50521,10 @@ function MaybeTraitScope({
50399
50521
  }
50400
50522
  return /* @__PURE__ */ jsx(Fragment, { children });
50401
50523
  }
50402
- function UISlotComponent({
50524
+ function UISlotComponent(props) {
50525
+ return /* @__PURE__ */ jsx(RenderSlotProvider, { slot: props.slot, children: /* @__PURE__ */ jsx(UISlotComponentInner, { ...props }) });
50526
+ }
50527
+ function UISlotComponentInner({
50403
50528
  slot,
50404
50529
  portal = false,
50405
50530
  position,