@almadar/ui 5.159.0 → 5.161.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/avl/index.js CHANGED
@@ -9,7 +9,7 @@ import { useTranslate } from '@almadar/ui/hooks';
9
9
  import { InMemoryPersistence, StateMachineManager, collectDeclaredConfigDefaults, resolveCallSitePayloadCaptures, createServerEffectHandlers, EffectExecutor, createContextFromBindings, createTickScheduler, isValidCronExpression, parseDurationString, normalizeCallSiteConfigToValues, interpolateValue } from '@almadar/runtime';
10
10
  import { FieldTypeSchema, isInlineTrait, isPageReference, buildResolvedTraitConfigs, schemaToIR, getPage, mergeEntityFrame, isCircuitEvent, applyListenPayloadMapping, walkSExpr, isRenderBindingMarker, containsEntityBinding, isSExpr, isEventPayloadValue, RENDER_BINDING_MARKER, isFileValue, ANIMATION_NAMES } from '@almadar/core';
11
11
  import * as LucideIcons2 from 'lucide-react';
12
- import { Loader2, X, Code, FileText, WrapText, Check, Copy, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ArrowLeft, ZoomOut, ZoomIn, Download, DollarSign, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, ChevronUp } from 'lucide-react';
12
+ import { Loader2, X, Code, FileText, WrapText, Check, Copy, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ArrowLeft, ZoomOut, ZoomIn, Download, DollarSign, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, HelpCircle, PauseCircle, Search, Bold, Italic, Underline, Strikethrough, Heading1, Heading2, Heading3, ListOrdered, Quote, Link as Link$1, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, ChevronUp } from 'lucide-react';
13
13
  import { createPortal } from 'react-dom';
14
14
  import { UISlotProvider, useUISlots, useTheme } from '@almadar/ui/context';
15
15
  import { evaluateGuard, evaluateListenPayloadExpr, evaluate, createMinimalContext, executeEffects } from '@almadar/evaluator';
@@ -28536,8 +28536,9 @@ function DataGrid({
28536
28536
  const titleField = fieldDefs.find((f3) => f3.variant === "h3" || f3.variant === "h4") ?? fieldDefs[0];
28537
28537
  const badgeFields = fieldDefs.filter((f3) => f3.variant === "badge" && f3 !== titleField);
28538
28538
  const bodyFields = fieldDefs.filter((f3) => f3 !== titleField && !badgeFields.includes(f3));
28539
- const primaryActions = actionDefs.filter((a) => a.variant !== "danger");
28540
- const dangerActions = actionDefs.filter((a) => a.variant === "danger");
28539
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
28540
+ const inlineCardActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
28541
+ const menuCardActions = actionDefs.filter((a) => !inlineCardActions.includes(a));
28541
28542
  const fireAction = (action, itemData) => {
28542
28543
  if (action.navigatesTo) {
28543
28544
  const url = action.navigatesTo.replace(
@@ -28557,6 +28558,9 @@ function DataGrid({
28557
28558
  e.stopPropagation();
28558
28559
  fireAction(action, itemData);
28559
28560
  };
28561
+ const cardClickAction = actionDefs.find((a) => a.variant !== "danger");
28562
+ const handleCardClick = cardClickAction ? (itemData) => () => fireAction(cardClickAction, itemData) : void 0;
28563
+ const stopCardClick = handleCardClick ? (e) => e.stopPropagation() : void 0;
28560
28564
  const hasRenderProp = typeof children === "function";
28561
28565
  useEffect(() => {
28562
28566
  if (data.length > 0 && !hasRenderProp && fieldDefs.length === 0) {
@@ -28628,55 +28632,40 @@ function DataGrid({
28628
28632
  {
28629
28633
  "data-entity-row": true,
28630
28634
  "data-entity-id": id,
28631
- className: cn("relative group/rowactions", isSelected && "ring-2 ring-primary rounded-lg"),
28635
+ onClick: handleCardClick?.(itemData),
28636
+ className: cn("relative group/rowactions", handleCardClick && "cursor-pointer", isSelected && "ring-2 ring-primary rounded-lg"),
28632
28637
  children: [
28633
28638
  children(itemData, index),
28634
- actionDefs.length > 0 && /* @__PURE__ */ jsxs(Box, { className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
28635
- /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: [
28636
- (maxInlineActions != null ? actionDefs.slice(0, maxInlineActions) : actionDefs).map((action, idx) => /* @__PURE__ */ jsxs(
28637
- Button,
28638
- {
28639
- variant: action.variant === "primary" ? "primary" : "ghost",
28640
- size: "sm",
28641
- onClick: handleActionClick(action, itemData),
28642
- "data-testid": `action-${action.event}`,
28643
- "data-row-id": String(itemData.id),
28644
- className: action.variant === "danger" ? "text-error hover:text-error hover:bg-error/10" : void 0,
28645
- children: [
28646
- action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
28647
- action.label
28648
- ]
28649
- },
28650
- idx
28651
- )),
28652
- maxInlineActions != null && actionDefs.length > maxInlineActions && /* @__PURE__ */ jsx(
28653
- Menu,
28654
- {
28655
- position: "bottom-end",
28656
- trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
28657
- items: actionDefs.slice(maxInlineActions).map((action) => ({
28658
- label: action.label,
28659
- icon: action.icon,
28660
- event: action.event,
28661
- onClick: () => fireAction(action, itemData)
28662
- }))
28663
- }
28664
- )
28665
- ] }),
28666
- /* @__PURE__ */ jsx(Box, { className: "hidden [@media(pointer:coarse)]:block rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: /* @__PURE__ */ jsx(
28639
+ actionDefs.length > 0 && /* @__PURE__ */ jsx(Box, { onClick: stopCardClick, className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: [
28640
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxs(
28641
+ Button,
28642
+ {
28643
+ variant: "primary",
28644
+ size: "sm",
28645
+ onClick: handleActionClick(action, itemData),
28646
+ "data-testid": `action-${action.event}`,
28647
+ "data-row-id": String(itemData.id),
28648
+ children: [
28649
+ action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
28650
+ action.label
28651
+ ]
28652
+ },
28653
+ idx
28654
+ )),
28655
+ menuCardActions.length > 0 && /* @__PURE__ */ jsx(
28667
28656
  Menu,
28668
28657
  {
28669
28658
  position: "bottom-end",
28670
28659
  trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
28671
- items: actionDefs.map((action) => ({
28660
+ items: menuCardActions.map((action) => ({
28672
28661
  label: action.label,
28673
28662
  icon: action.icon,
28674
- event: action.event,
28663
+ variant: action.variant === "danger" ? "danger" : "default",
28675
28664
  onClick: () => fireAction(action, itemData)
28676
28665
  }))
28677
28666
  }
28678
- ) })
28679
- ] })
28667
+ )
28668
+ ] }) })
28680
28669
  ]
28681
28670
  },
28682
28671
  id
@@ -28690,12 +28679,14 @@ function DataGrid({
28690
28679
  {
28691
28680
  "data-entity-row": true,
28692
28681
  "data-entity-id": id,
28682
+ onClick: handleCardClick?.(itemData),
28693
28683
  className: cn(
28694
28684
  "bg-card rounded-lg",
28695
28685
  "border border-border",
28696
28686
  "shadow-elevation-card hover:shadow-elevation-dialog",
28697
28687
  "hover:border-primary transition-all",
28698
28688
  "flex flex-col",
28689
+ handleCardClick && "cursor-pointer",
28699
28690
  isSelected && "ring-2 ring-primary border-primary"
28700
28691
  ),
28701
28692
  children: [
@@ -28745,53 +28736,35 @@ function DataGrid({
28745
28736
  ] }, field.name);
28746
28737
  }) })
28747
28738
  ] }),
28748
- (primaryActions.length > 0 || dangerActions.length > 0) && /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
28749
- (maxInlineActions != null ? primaryActions.slice(0, maxInlineActions) : primaryActions).map((action, idx) => /* @__PURE__ */ jsx(
28739
+ actionDefs.length > 0 && /* @__PURE__ */ jsxs(HStack, { gap: "xs", onClick: stopCardClick, className: "flex-shrink-0", children: [
28740
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsx(
28750
28741
  Button,
28751
28742
  {
28752
- variant: action.variant === "primary" ? "primary" : "ghost",
28743
+ variant: "primary",
28753
28744
  size: "sm",
28754
28745
  onClick: handleActionClick(action, itemData),
28755
28746
  "data-testid": `action-${action.event}`,
28756
28747
  "data-row-id": String(itemData.id),
28757
28748
  "aria-label": action.label,
28758
28749
  title: action.label,
28759
- className: cn(
28760
- action.variant === "primary" ? void 0 : "text-muted-foreground hover:text-foreground",
28761
- action.icon && "px-2"
28762
- ),
28750
+ className: cn(action.icon && "px-2"),
28763
28751
  children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
28764
28752
  },
28765
28753
  idx
28766
28754
  )),
28767
- maxInlineActions != null && primaryActions.length > maxInlineActions && /* @__PURE__ */ jsx(
28755
+ menuCardActions.length > 0 && /* @__PURE__ */ jsx(
28768
28756
  Menu,
28769
28757
  {
28770
28758
  position: "bottom-end",
28771
28759
  trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
28772
- items: primaryActions.slice(maxInlineActions).map((action) => ({
28760
+ items: menuCardActions.map((action) => ({
28773
28761
  label: action.label,
28774
28762
  icon: action.icon,
28775
- event: action.event,
28763
+ variant: action.variant === "danger" ? "danger" : "default",
28776
28764
  onClick: () => fireAction(action, itemData)
28777
28765
  }))
28778
28766
  }
28779
- ),
28780
- dangerActions.map((action, idx) => /* @__PURE__ */ jsx(
28781
- Button,
28782
- {
28783
- variant: "ghost",
28784
- size: "sm",
28785
- onClick: handleActionClick(action, itemData),
28786
- "data-testid": `action-${action.event}`,
28787
- "data-row-id": String(itemData.id),
28788
- "aria-label": action.label,
28789
- title: action.label,
28790
- className: "text-error hover:text-error hover:bg-error/10 px-2",
28791
- children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
28792
- },
28793
- `danger-${idx}`
28794
- ))
28767
+ )
28795
28768
  ] })
28796
28769
  ] }) }),
28797
28770
  bodyFields.length > 0 && /* @__PURE__ */ jsx(Box, { className: "px-4 pt-2 pb-4 flex-1", children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
@@ -28812,8 +28785,8 @@ function DataGrid({
28812
28785
  }
28813
28786
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
28814
28787
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
28815
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
28816
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue(value, field.format) })
28788
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
28789
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
28817
28790
  ] }, field.name);
28818
28791
  }) })
28819
28792
  ] }) })
@@ -29033,54 +29006,59 @@ function DataList({
29033
29006
  if (!itemActions || itemActions.length === 0) return null;
29034
29007
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
29035
29008
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
29036
- return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
29037
- inline.map((action, idx) => /* @__PURE__ */ jsxs(
29038
- Button,
29039
- {
29040
- variant: action.variant ?? "ghost",
29041
- size: "sm",
29042
- onClick: handleActionClick(action, itemData),
29043
- "data-testid": `action-${action.event}`,
29044
- "data-row-id": String(itemData.id),
29045
- className: cn(
29046
- action.variant === "danger" && "text-error hover:bg-error/10",
29047
- // Must sit on the Button itself: the variant's own text colour
29048
- // beats an inherited one from the row wrapper.
29049
- onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
29050
- ),
29051
- children: [
29052
- action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
29053
- action.label
29054
- ]
29055
- },
29056
- idx
29057
- )),
29058
- overflow.length > 0 && /* @__PURE__ */ jsx(
29059
- Menu,
29060
- {
29061
- position: "bottom-end",
29062
- trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
29063
- items: overflow.map((action) => ({
29064
- label: action.label,
29065
- icon: action.icon,
29066
- event: action.event,
29067
- variant: action.variant === "danger" ? "danger" : "default",
29068
- onClick: () => eventBus.emit(`UI:${action.event}`, {
29069
- id: itemData.id,
29070
- row: itemData
29071
- })
29072
- }))
29073
- }
29074
- )
29075
- ] });
29009
+ return (
29010
+ // stopPropagation at the cluster: the "⋯" trigger opens the overflow
29011
+ // menu without also firing the row's default click (inline buttons
29012
+ // already stop it in handleActionClick; the Menu panel is portaled).
29013
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, className: "flex-shrink-0", children: [
29014
+ inline.map((action, idx) => /* @__PURE__ */ jsxs(
29015
+ Button,
29016
+ {
29017
+ variant: action.variant ?? "ghost",
29018
+ size: "sm",
29019
+ onClick: handleActionClick(action, itemData),
29020
+ "data-testid": `action-${action.event}`,
29021
+ "data-row-id": String(itemData.id),
29022
+ className: cn(
29023
+ action.variant === "danger" && "text-error hover:bg-error/10",
29024
+ // Must sit on the Button itself: the variant's own text colour
29025
+ // beats an inherited one from the row wrapper.
29026
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
29027
+ ),
29028
+ children: [
29029
+ action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
29030
+ action.label
29031
+ ]
29032
+ },
29033
+ idx
29034
+ )),
29035
+ overflow.length > 0 && /* @__PURE__ */ jsx(
29036
+ Menu,
29037
+ {
29038
+ position: "bottom-end",
29039
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
29040
+ items: overflow.map((action) => ({
29041
+ label: action.label,
29042
+ icon: action.icon,
29043
+ variant: action.variant === "danger" ? "danger" : "default",
29044
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
29045
+ id: itemData.id,
29046
+ row: itemData
29047
+ })
29048
+ }))
29049
+ }
29050
+ )
29051
+ ] })
29052
+ );
29076
29053
  };
29054
+ const rowClickEvent = itemClickEvent || itemActions?.find((a) => a.variant !== "danger")?.event;
29077
29055
  const handleRowClick = (itemData) => () => {
29078
- if (!itemClickEvent) return;
29056
+ if (!rowClickEvent) return;
29079
29057
  const payload = {
29080
29058
  id: itemData.id,
29081
29059
  row: itemData
29082
29060
  };
29083
- eventBus.emit(`UI:${itemClickEvent}`, payload);
29061
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
29084
29062
  };
29085
29063
  if (isLoading) {
29086
29064
  return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
@@ -29128,10 +29106,10 @@ function DataList({
29128
29106
  {
29129
29107
  "data-entity-row": true,
29130
29108
  "data-entity-id": id,
29131
- onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
29109
+ onClick: rowClickEvent ? handleRowClick(itemData) : void 0,
29132
29110
  className: cn(
29133
29111
  "flex px-4 group/rowactions",
29134
- itemClickEvent && "cursor-pointer",
29112
+ rowClickEvent && "cursor-pointer",
29135
29113
  isSent ? "justify-end" : "justify-start"
29136
29114
  ),
29137
29115
  children: /* @__PURE__ */ jsxs(
@@ -29207,7 +29185,7 @@ function DataList({
29207
29185
  const id2 = itemData.id || String(index);
29208
29186
  const actions = renderItemActions(itemData);
29209
29187
  return wrapDnd(
29210
- /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id2, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", itemClickEvent && "cursor-pointer"), children: [
29188
+ /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id2, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", rowClickEvent && "cursor-pointer"), children: [
29211
29189
  children(itemData, index),
29212
29190
  actions && /* @__PURE__ */ jsxs(Box, { className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
29213
29191
  /* @__PURE__ */ jsx(Box, { className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: actions }),
@@ -29219,7 +29197,6 @@ function DataList({
29219
29197
  items: (itemActions ?? []).map((action) => ({
29220
29198
  label: action.label,
29221
29199
  icon: action.icon,
29222
- event: action.event,
29223
29200
  variant: action.variant === "danger" ? "danger" : "default",
29224
29201
  onClick: () => eventBus.emit(`UI:${action.event}`, {
29225
29202
  id: itemData.id,
@@ -29236,12 +29213,16 @@ function DataList({
29236
29213
  const id = itemData.id || String(index);
29237
29214
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
29238
29215
  return wrapDnd(
29239
- /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn(itemClickEvent && "cursor-pointer"), children: [
29216
+ /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
29240
29217
  /* @__PURE__ */ jsxs(
29241
29218
  Box,
29242
29219
  {
29243
29220
  className: cn(
29244
- "group flex items-center gap-4 transition-all duration-fast",
29221
+ // items-start, not items-center: a multi-line row (title + meta +
29222
+ // progress) centred its action cluster in the vertical middle, so
29223
+ // the buttons floated between the meta fields instead of anchoring
29224
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
29225
+ "group flex items-start gap-4 transition-all duration-fast",
29245
29226
  isCompact ? "px-4 py-2" : "px-6 py-4",
29246
29227
  "hover:bg-muted/80",
29247
29228
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -29272,11 +29253,19 @@ function DataList({
29272
29253
  if (value === void 0 || value === null || value === "") return null;
29273
29254
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
29274
29255
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
29275
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
29276
- field.label ?? fieldLabel3(field.name),
29277
- ":"
29278
- ] }),
29279
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
29256
+ /* @__PURE__ */ jsxs(
29257
+ Typography,
29258
+ {
29259
+ variant: "caption",
29260
+ color: "secondary",
29261
+ className: cn(field.format !== "boolean" && "sr-only"),
29262
+ children: [
29263
+ field.label ?? fieldLabel3(field.name),
29264
+ ":"
29265
+ ]
29266
+ }
29267
+ ),
29268
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
29280
29269
  ] }, field.name);
29281
29270
  }) }),
29282
29271
  progressFields.map((field) => {
@@ -29295,7 +29284,7 @@ function DataList({
29295
29284
  ]
29296
29285
  }
29297
29286
  ),
29298
- isCard && !isLast && /* @__PURE__ */ jsx(Box, { className: "mx-6 border-b border-border/40" })
29287
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
29299
29288
  ] }, id)
29300
29289
  );
29301
29290
  };
@@ -29305,7 +29294,13 @@ function DataList({
29305
29294
  {
29306
29295
  className: cn(
29307
29296
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
29308
- !isCard && gapClass,
29297
+ // `gap-*` is inert on a block container, and Box only emits a display
29298
+ // class when its `display` prop is set — so every non-card list had
29299
+ // been asking for a gap that CSS silently dropped. flex-col makes it
29300
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
29301
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
29302
+ !isCard && "flex flex-col",
29303
+ !isCard && !isCompact && gapClass,
29309
29304
  listLookStyles[look],
29310
29305
  className
29311
29306
  ),
@@ -34494,7 +34489,7 @@ var init_MapView = __esm({
34494
34489
  shadowSize: [41, 41]
34495
34490
  });
34496
34491
  L.Marker.prototype.options.icon = defaultIcon;
34497
- const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback96, useState: useState100 } = React94__default;
34492
+ const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback96, useState: useState102 } = React94__default;
34498
34493
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
34499
34494
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
34500
34495
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -34539,7 +34534,7 @@ var init_MapView = __esm({
34539
34534
  showAttribution = true
34540
34535
  }) {
34541
34536
  const eventBus = useEventBus2();
34542
- const [clickedPosition, setClickedPosition] = useState100(null);
34537
+ const [clickedPosition, setClickedPosition] = useState102(null);
34543
34538
  const handleMapClick = useCallback96((lat, lng) => {
34544
34539
  if (showClickedPin) {
34545
34540
  setClickedPosition({ lat, lng });
@@ -38901,741 +38896,775 @@ var init_PositionedCanvas = __esm({
38901
38896
  PositionedCanvas.displayName = "PositionedCanvas";
38902
38897
  }
38903
38898
  });
38904
- function nextBlockId(prefix = "blk") {
38905
- _idSeq += 1;
38906
- const random = Math.random().toString(36).slice(2, 8);
38907
- return `${prefix}-${Date.now().toString(36)}-${_idSeq}-${random}`;
38908
- }
38909
- function normalizeBlocks(raw) {
38910
- if (!Array.isArray(raw) || raw.length === 0) return [createBlock("paragraph")];
38911
- return raw.map((row) => {
38912
- const entity = row;
38913
- const rawType = entity.type;
38914
- const type = typeof rawType === "string" && BLOCK_TYPES.has(rawType) ? rawType : "paragraph";
38915
- const id = typeof entity.id === "string" && entity.id ? entity.id : nextBlockId(type);
38916
- return { ...entity, id, type };
38917
- });
38918
- }
38919
- function createBlock(type) {
38920
- switch (type) {
38921
- case "bullet-list":
38922
- case "numbered-list":
38923
- return {
38924
- id: nextBlockId(type),
38925
- type,
38926
- children: [
38927
- { id: nextBlockId("li"), type: "paragraph", content: "" }
38928
- ]
38929
- };
38930
- case "image":
38931
- return {
38932
- id: nextBlockId(type),
38933
- type,
38934
- content: "",
38935
- metadata: { url: "", caption: "" }
38936
- };
38937
- case "code":
38938
- return {
38939
- id: nextBlockId(type),
38940
- type,
38941
- content: "",
38942
- metadata: { language: "plaintext" }
38943
- };
38944
- case "divider":
38945
- return { id: nextBlockId(type), type };
38946
- default:
38947
- return { id: nextBlockId(type), type, content: "" };
38899
+ function safeUrl(raw, kinds) {
38900
+ const url = raw.trim();
38901
+ if (url.startsWith("#") || url.startsWith("/")) return url;
38902
+ const lower = url.toLowerCase();
38903
+ for (const kind of kinds) {
38904
+ if (lower.startsWith(kind)) return url;
38948
38905
  }
38906
+ return null;
38949
38907
  }
38950
- function replaceBlock(blocks, id, updater) {
38951
- return blocks.map((block) => block.id === id ? updater(block) : block);
38952
- }
38953
- function removeBlock(blocks, id) {
38954
- return blocks.filter((block) => block.id !== id);
38955
- }
38956
- function duplicateBlock(block) {
38957
- return {
38958
- ...block,
38959
- id: nextBlockId(block.type),
38960
- children: block.children?.map((child) => ({
38961
- ...child,
38962
- id: nextBlockId("li")
38963
- })),
38964
- metadata: block.metadata ? { ...block.metadata } : void 0
38965
- };
38966
- }
38967
- function insertAfter(blocks, targetId, inserted) {
38968
- const idx = blocks.findIndex((b) => b.id === targetId);
38969
- if (idx === -1) return [...blocks, inserted];
38970
- const next = blocks.slice();
38971
- next.splice(idx + 1, 0, inserted);
38972
- return next;
38973
- }
38974
- function changeBlockType(block, type) {
38975
- if (block.type === type) return block;
38976
- if (type === "bullet-list" || type === "numbered-list") {
38977
- if (block.children && block.children.length > 0) {
38978
- return { ...block, type };
38908
+ function sanitizeNode(el) {
38909
+ const children = Array.from(el.children);
38910
+ for (const child of children) {
38911
+ const tag = child.tagName.toLowerCase();
38912
+ if (DROP_TAGS.has(tag)) {
38913
+ child.remove();
38914
+ continue;
38979
38915
  }
38980
- const seed2 = block.content ?? "";
38981
- return {
38982
- id: block.id,
38983
- type,
38984
- children: [
38985
- { id: nextBlockId("li"), type: "paragraph", content: seed2 }
38986
- ]
38987
- };
38988
- }
38989
- if (type === "divider") {
38990
- return { id: block.id, type };
38916
+ const allowed = ALLOWED_ATTRS[tag];
38917
+ if (!allowed) {
38918
+ sanitizeNode(child);
38919
+ child.replaceWith(...Array.from(child.childNodes));
38920
+ continue;
38921
+ }
38922
+ for (const attr of Array.from(child.attributes)) {
38923
+ const name = attr.name.toLowerCase();
38924
+ if (!allowed.includes(name)) {
38925
+ child.removeAttribute(attr.name);
38926
+ continue;
38927
+ }
38928
+ if (name === "href") {
38929
+ const url = safeUrl(attr.value, ["http://", "https://", "mailto:"]);
38930
+ if (url === null) child.removeAttribute(attr.name);
38931
+ else child.setAttribute("href", url);
38932
+ }
38933
+ if (name === "src") {
38934
+ const url = safeUrl(attr.value, ["http://", "https://", "data:image/"]);
38935
+ if (url === null) child.remove();
38936
+ else child.setAttribute("src", url);
38937
+ }
38938
+ }
38939
+ sanitizeNode(child);
38991
38940
  }
38992
- if (type === "image") {
38993
- return {
38994
- id: block.id,
38995
- type,
38996
- content: "",
38997
- metadata: { url: "", caption: block.content ?? "" }
38998
- };
38941
+ }
38942
+ function sanitizeRichHtml(html) {
38943
+ if (!html || typeof html !== "string") return "";
38944
+ if (typeof window === "undefined" || typeof DOMParser === "undefined") return "";
38945
+ const doc = new DOMParser().parseFromString(html, "text/html");
38946
+ sanitizeNode(doc.body);
38947
+ if (!doc.body.querySelector("img, hr") && (doc.body.textContent ?? "").trim().length === 0) {
38948
+ return "";
38999
38949
  }
39000
- if (type === "code") {
39001
- return {
39002
- id: block.id,
39003
- type,
39004
- content: block.content ?? "",
39005
- metadata: { language: "plaintext" }
39006
- };
38950
+ return doc.body.innerHTML.replace(/​/g, "");
38951
+ }
38952
+ function htmlIsEmpty(el) {
38953
+ if (el.querySelector("img, hr")) return false;
38954
+ return (el.textContent ?? "").trim().length === 0;
38955
+ }
38956
+ function closestBlock(node, root) {
38957
+ let cur = node;
38958
+ while (cur && cur !== root) {
38959
+ if (cur instanceof Element && BLOCK_TAGS.has(cur.tagName)) return cur;
38960
+ cur = cur.parentNode;
39007
38961
  }
39008
- const seed = block.children?.[0]?.content ?? block.content ?? "";
39009
- return { id: block.id, type, content: seed };
38962
+ return null;
39010
38963
  }
39011
- function BlockMenu({ block, readOnly, onDelete, onDuplicate, onChangeType }) {
39012
- const { t } = useTranslate();
39013
- const [open, setOpen] = useState(false);
39014
- const ref = useRef(null);
39015
- useEffect(() => {
39016
- if (!open) return;
39017
- function onDocClick(e) {
39018
- if (ref.current && !ref.current.contains(e.target)) {
39019
- setOpen(false);
38964
+ function RichTextStyles() {
38965
+ return /* @__PURE__ */ jsx("style", { children: RICH_TEXT_CSS });
38966
+ }
38967
+ function readToolbarState(root) {
38968
+ const sel = window.getSelection();
38969
+ if (!sel || sel.rangeCount === 0) return IDLE_TOOLBAR;
38970
+ const anchor = sel.anchorNode;
38971
+ if (!anchor || !root.contains(anchor)) return IDLE_TOOLBAR;
38972
+ let block = null;
38973
+ let link = false;
38974
+ let node = anchor;
38975
+ while (node && node !== root) {
38976
+ if (node instanceof Element) {
38977
+ const tag = node.tagName.toLowerCase();
38978
+ if (!block && (tag === "h1" || tag === "h2" || tag === "h3" || tag === "blockquote" || tag === "pre")) {
38979
+ block = tag;
39020
38980
  }
38981
+ if (tag === "a") link = true;
39021
38982
  }
39022
- document.addEventListener("mousedown", onDocClick);
39023
- return () => document.removeEventListener("mousedown", onDocClick);
39024
- }, [open]);
39025
- if (readOnly) return null;
39026
- return /* @__PURE__ */ jsxs(Box, { ref, className: "relative", children: [
39027
- /* @__PURE__ */ jsx(
39028
- Button,
39029
- {
39030
- type: "button",
39031
- variant: "ghost",
39032
- "aria-label": t("richBlockEditor.blockActions"),
39033
- className: cn(
39034
- "inline-flex items-center justify-center",
39035
- "h-6 w-6 rounded-sm p-0 gap-0",
39036
- "text-muted-foreground hover:bg-muted hover:text-foreground",
39037
- "opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
39038
- "transition-opacity"
39039
- ),
39040
- onClick: () => setOpen((v) => !v),
39041
- children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", className: "w-3.5 h-3.5" })
39042
- }
39043
- ),
39044
- open && /* @__PURE__ */ jsxs(
39045
- Box,
39046
- {
39047
- role: "menu",
39048
- className: cn(
39049
- "absolute right-0 z-10 mt-1 w-44",
39050
- "rounded-container border border-border bg-popover shadow-elevation-popover",
39051
- "py-1 text-sm"
39052
- ),
39053
- children: [
39054
- /* @__PURE__ */ jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t(BLOCK_TYPE_LABEL_KEY[block.type]) }),
39055
- /* @__PURE__ */ jsxs(
39056
- Button,
39057
- {
39058
- type: "button",
39059
- variant: "ghost",
39060
- role: "menuitem",
39061
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
39062
- onClick: () => {
39063
- onDuplicate();
39064
- setOpen(false);
39065
- },
39066
- children: [
39067
- /* @__PURE__ */ jsx(Icon, { name: "plus", className: "w-3.5 h-3.5" }),
39068
- " ",
39069
- t("richBlockEditor.duplicate")
39070
- ]
39071
- }
39072
- ),
39073
- /* @__PURE__ */ jsxs(
39074
- Button,
39075
- {
39076
- type: "button",
39077
- variant: "ghost",
39078
- role: "menuitem",
39079
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-destructive hover:bg-muted justify-start rounded-none",
39080
- onClick: () => {
39081
- onDelete();
39082
- setOpen(false);
39083
- },
39084
- children: [
39085
- /* @__PURE__ */ jsx(Icon, { name: "trash", className: "w-3.5 h-3.5" }),
39086
- " ",
39087
- t("common.delete")
39088
- ]
39089
- }
39090
- ),
39091
- CHANGEABLE_TYPES.includes(block.type) && /* @__PURE__ */ jsxs(Fragment, { children: [
39092
- /* @__PURE__ */ jsx(Box, { className: "my-1 border-t border-border" }),
39093
- /* @__PURE__ */ jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t("richBlockEditor.turnInto") }),
39094
- CHANGEABLE_TYPES.filter((bt) => bt !== block.type).map((bt) => /* @__PURE__ */ jsx(
39095
- Button,
39096
- {
39097
- type: "button",
39098
- variant: "ghost",
39099
- role: "menuitem",
39100
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
39101
- onClick: () => {
39102
- onChangeType(bt);
39103
- setOpen(false);
39104
- },
39105
- children: t(BLOCK_TYPE_LABEL_KEY[bt])
39106
- },
39107
- bt
39108
- ))
39109
- ] })
39110
- ]
39111
- }
39112
- )
39113
- ] });
38983
+ node = node.parentNode;
38984
+ }
38985
+ return {
38986
+ bold: document.queryCommandState("bold"),
38987
+ italic: document.queryCommandState("italic"),
38988
+ underline: document.queryCommandState("underline"),
38989
+ strikeThrough: document.queryCommandState("strikeThrough"),
38990
+ block,
38991
+ bullets: document.queryCommandState("insertUnorderedList"),
38992
+ numbers: document.queryCommandState("insertOrderedList"),
38993
+ link
38994
+ };
39114
38995
  }
39115
- function Editable({
39116
- tag,
39117
- value,
39118
- readOnly,
39119
- placeholder,
39120
- className,
39121
- ariaLabel,
39122
- onValueChange
39123
- }) {
39124
- const ref = useRef(null);
39125
- useEffect(() => {
39126
- const el = ref.current;
39127
- if (!el) return;
39128
- const isFocused = document.activeElement === el;
39129
- if (!isFocused && el.textContent !== value) {
39130
- el.textContent = value;
39131
- }
39132
- }, [value]);
39133
- const handleInput = useCallback(
39134
- (e) => {
39135
- onValueChange(e.currentTarget.textContent ?? "");
39136
- },
39137
- [onValueChange]
39138
- );
38996
+ function ToolbarButton({ icon: IconCmp, label, active, onExec }) {
39139
38997
  return /* @__PURE__ */ jsx(
39140
- Box,
38998
+ Button,
39141
38999
  {
39142
- as: tag,
39143
- ref,
39144
- contentEditable: !readOnly,
39145
- suppressContentEditableWarning: true,
39146
- role: readOnly ? void 0 : "textbox",
39147
- "aria-label": ariaLabel,
39148
- "aria-multiline": "true",
39149
- "data-placeholder": placeholder,
39000
+ type: "button",
39001
+ variant: "ghost",
39002
+ size: "sm",
39003
+ "aria-label": label,
39004
+ "aria-pressed": active,
39005
+ title: label,
39150
39006
  className: cn(
39151
- "outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-sm",
39152
- "empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground/60",
39153
- className
39007
+ "h-8 w-8 p-0 gap-0 justify-center",
39008
+ active && "bg-muted text-foreground"
39154
39009
  ),
39155
- onInput: handleInput
39010
+ onMouseDown: (e) => {
39011
+ e.preventDefault();
39012
+ onExec();
39013
+ },
39014
+ children: /* @__PURE__ */ jsx(IconCmp, { size: 15 })
39156
39015
  }
39157
39016
  );
39158
39017
  }
39159
- function BlockRow({
39160
- block,
39161
- readOnly,
39162
- showAffordances,
39163
- placeholder,
39164
- onUpdate,
39165
- onDelete,
39166
- onDuplicate,
39167
- onChangeType
39168
- }) {
39169
- const { t } = useTranslate();
39170
- const setContent = useCallback(
39171
- (next) => onUpdate((b) => ({ ...b, content: next })),
39172
- [onUpdate]
39173
- );
39174
- const setMetadata = useCallback(
39175
- (key, value) => onUpdate((b) => ({
39176
- ...b,
39177
- metadata: { ...b.metadata ?? {}, [key]: value }
39178
- })),
39179
- [onUpdate]
39180
- );
39181
- const setChildContent = useCallback(
39182
- (childId, next) => onUpdate((b) => ({
39183
- ...b,
39184
- children: (b.children ?? []).map(
39185
- (c) => c.id === childId ? { ...c, content: next } : c
39186
- )
39187
- })),
39188
- [onUpdate]
39189
- );
39190
- const addListItem = useCallback(
39191
- () => onUpdate((b) => ({
39192
- ...b,
39193
- children: [
39194
- ...b.children ?? [],
39195
- { id: nextBlockId("li"), type: "paragraph", content: "" }
39196
- ]
39197
- })),
39198
- [onUpdate]
39199
- );
39200
- const removeListItem = useCallback(
39201
- (childId) => onUpdate((b) => {
39202
- const remaining = (b.children ?? []).filter((c) => c.id !== childId);
39203
- return {
39204
- ...b,
39205
- children: remaining.length === 0 ? [{ id: nextBlockId("li"), type: "paragraph", content: "" }] : remaining
39206
- };
39207
- }),
39208
- [onUpdate]
39209
- );
39210
- const renderBody = () => {
39211
- switch (block.type) {
39212
- case "heading-1":
39213
- return /* @__PURE__ */ jsx(
39214
- Editable,
39215
- {
39216
- tag: "h1",
39217
- value: block.content ?? "",
39218
- readOnly,
39219
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading1"),
39220
- ariaLabel: t("richBlockEditor.aria.heading1Block"),
39221
- className: "text-3xl font-bold leading-tight",
39222
- onValueChange: setContent
39223
- }
39224
- );
39225
- case "heading-2":
39226
- return /* @__PURE__ */ jsx(
39227
- Editable,
39228
- {
39229
- tag: "h2",
39230
- value: block.content ?? "",
39231
- readOnly,
39232
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading2"),
39233
- ariaLabel: t("richBlockEditor.aria.heading2Block"),
39234
- className: "text-2xl font-semibold leading-tight",
39235
- onValueChange: setContent
39236
- }
39237
- );
39238
- case "heading-3":
39239
- return /* @__PURE__ */ jsx(
39240
- Editable,
39241
- {
39242
- tag: "h3",
39243
- value: block.content ?? "",
39244
- readOnly,
39245
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading3"),
39246
- ariaLabel: t("richBlockEditor.aria.heading3Block"),
39247
- className: "text-xl font-semibold leading-tight",
39248
- onValueChange: setContent
39249
- }
39250
- );
39251
- case "quote":
39252
- return /* @__PURE__ */ jsx(
39253
- Editable,
39254
- {
39255
- tag: "blockquote",
39256
- value: block.content ?? "",
39257
- readOnly,
39258
- placeholder: placeholder ?? t("richBlockEditor.placeholder.quote"),
39259
- ariaLabel: t("richBlockEditor.aria.quoteBlock"),
39260
- className: "border-l-4 border-primary/60 pl-4 italic text-muted-foreground",
39261
- onValueChange: setContent
39262
- }
39263
- );
39264
- case "code":
39265
- return /* @__PURE__ */ jsxs(Box, { className: "rounded-md border border-border bg-muted/40", children: [
39266
- /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between border-b border-border px-3 py-1 text-xs text-muted-foreground", children: [
39267
- /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "uppercase tracking-wide", children: t("richBlockEditor.blockType.code") }),
39268
- !readOnly && /* @__PURE__ */ jsx(
39269
- Input,
39270
- {
39271
- inputType: "text",
39272
- value: String(block.metadata?.language ?? "plaintext"),
39273
- "aria-label": t("richBlockEditor.aria.codeLanguage"),
39274
- className: cn(
39275
- "h-6 w-32 rounded-sm border border-border bg-background",
39276
- "px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
39277
- ),
39278
- onChange: (e) => setMetadata("language", e.target.value)
39279
- }
39280
- ),
39281
- readOnly && /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "text-xs", children: String(block.metadata?.language ?? "plaintext") })
39282
- ] }),
39283
- /* @__PURE__ */ jsx(
39284
- Editable,
39285
- {
39286
- tag: "pre",
39287
- value: block.content ?? "",
39288
- readOnly,
39289
- placeholder: placeholder ?? t("richBlockEditor.placeholder.code"),
39290
- ariaLabel: t("richBlockEditor.aria.codeBlock"),
39291
- className: "block whitespace-pre-wrap p-3 font-mono text-sm leading-relaxed",
39292
- onValueChange: setContent
39293
- }
39294
- )
39295
- ] });
39296
- case "divider":
39297
- return /* @__PURE__ */ jsx(Divider, { className: "my-2" });
39298
- case "image": {
39299
- const url = String(block.metadata?.url ?? "");
39300
- const caption = String(block.metadata?.caption ?? "");
39301
- const imgProps = {
39302
- src: url,
39303
- alt: caption || t("richBlockEditor.embeddedImage"),
39304
- className: "max-h-96 w-full rounded-md border border-border object-contain"
39018
+ var CHANGE_DEBOUNCE_MS, ALLOWED_ATTRS, DROP_TAGS, MD_BLOCK_PREFIXES, BLOCK_TAGS, MD_INLINE_RULES, RICH_TEXT_CSS, IDLE_TOOLBAR, RichTextEditor;
39019
+ var init_RichTextEditor = __esm({
39020
+ "components/core/molecules/RichTextEditor.tsx"() {
39021
+ "use client";
39022
+ init_cn();
39023
+ init_Box();
39024
+ init_Button();
39025
+ init_useEventBus();
39026
+ CHANGE_DEBOUNCE_MS = 400;
39027
+ ALLOWED_ATTRS = {
39028
+ p: [],
39029
+ h1: [],
39030
+ h2: [],
39031
+ h3: [],
39032
+ h4: [],
39033
+ ul: [],
39034
+ ol: [],
39035
+ li: [],
39036
+ blockquote: [],
39037
+ pre: [],
39038
+ code: [],
39039
+ b: [],
39040
+ strong: [],
39041
+ i: [],
39042
+ em: [],
39043
+ u: [],
39044
+ s: [],
39045
+ strike: [],
39046
+ br: [],
39047
+ hr: [],
39048
+ div: [],
39049
+ span: [],
39050
+ a: ["href"],
39051
+ img: ["src", "alt"]
39052
+ };
39053
+ DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "link", "meta", "form", "input", "button", "textarea", "select"]);
39054
+ MD_BLOCK_PREFIXES = {
39055
+ "#": { kind: "block", tag: "h1" },
39056
+ "##": { kind: "block", tag: "h2" },
39057
+ "###": { kind: "block", tag: "h3" },
39058
+ ">": { kind: "block", tag: "blockquote" },
39059
+ "```": { kind: "block", tag: "pre" },
39060
+ "-": { kind: "list", command: "insertUnorderedList" },
39061
+ "*": { kind: "list", command: "insertUnorderedList" },
39062
+ "1.": { kind: "list", command: "insertOrderedList" }
39063
+ };
39064
+ BLOCK_TAGS = /* @__PURE__ */ new Set(["P", "DIV", "LI", "H1", "H2", "H3", "H4", "BLOCKQUOTE", "PRE"]);
39065
+ MD_INLINE_RULES = [
39066
+ { pattern: /\*\*([^*\n]+)\*\*$/, wrap: "strong" },
39067
+ { pattern: /(^|[^*])\*([^*\n]+)\*$/, wrap: "em" },
39068
+ { pattern: /`([^`\n]+)`$/, wrap: "code" }
39069
+ ];
39070
+ RICH_TEXT_CSS = `
39071
+ .almadar-rich-text { line-height: 1.7; color: var(--color-foreground); }
39072
+ .almadar-rich-text h1 { font-size: 1.875rem; font-weight: 700; line-height: 1.25; margin: 1.25em 0 0.4em; }
39073
+ .almadar-rich-text h2 { font-size: 1.5rem; font-weight: 650; line-height: 1.3; margin: 1.1em 0 0.4em; }
39074
+ .almadar-rich-text h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.35; margin: 1em 0 0.35em; }
39075
+ .almadar-rich-text h1:first-child, .almadar-rich-text h2:first-child, .almadar-rich-text h3:first-child { margin-top: 0; }
39076
+ .almadar-rich-text p { margin: 0.5em 0; }
39077
+ .almadar-rich-text ul { list-style: disc; padding-inline-start: 1.5rem; margin: 0.5em 0; }
39078
+ .almadar-rich-text ol { list-style: decimal; padding-inline-start: 1.5rem; margin: 0.5em 0; }
39079
+ .almadar-rich-text li { margin: 0.25em 0; }
39080
+ .almadar-rich-text blockquote { border-inline-start: 3px solid var(--color-primary); padding-inline-start: 1rem; font-style: italic; color: var(--color-muted-foreground); margin: 0.75em 0; }
39081
+ .almadar-rich-text pre { background: var(--color-muted); border: 1px solid var(--color-border); border-radius: 0.375rem; padding: 0.75rem 1rem; font-family: ui-monospace, monospace; font-size: 0.875em; white-space: pre-wrap; margin: 0.75em 0; }
39082
+ .almadar-rich-text code { font-family: ui-monospace, monospace; font-size: 0.875em; background: var(--color-muted); border-radius: 0.25rem; padding: 0.1em 0.35em; }
39083
+ .almadar-rich-text pre code { background: transparent; padding: 0; }
39084
+ .almadar-rich-text a { color: var(--color-primary); text-decoration: underline; }
39085
+ .almadar-rich-text hr { border: none; border-top: 1px solid var(--color-border); margin: 1.25em 0; }
39086
+ .almadar-rich-text img { max-width: 100%; border-radius: 0.375rem; }
39087
+ `;
39088
+ IDLE_TOOLBAR = {
39089
+ bold: false,
39090
+ italic: false,
39091
+ underline: false,
39092
+ strikeThrough: false,
39093
+ block: null,
39094
+ bullets: false,
39095
+ numbers: false,
39096
+ link: false
39097
+ };
39098
+ RichTextEditor = ({
39099
+ value,
39100
+ onChange,
39101
+ changeEvent,
39102
+ readOnly = false,
39103
+ placeholder,
39104
+ showToolbar = true,
39105
+ className
39106
+ }) => {
39107
+ const { t } = useTranslate();
39108
+ const ref = useRef(null);
39109
+ const [toolbar, setToolbar] = useState(IDLE_TOOLBAR);
39110
+ const [empty, setEmpty] = useState(() => !value || !sanitizeRichHtml(value).trim());
39111
+ const eventBus = useEventBus();
39112
+ const onChangeRef = useRef(onChange);
39113
+ const changeEventRef = useRef(changeEvent);
39114
+ useEffect(() => {
39115
+ onChangeRef.current = onChange;
39116
+ changeEventRef.current = changeEvent;
39117
+ }, [onChange, changeEvent]);
39118
+ const emitTimerRef = useRef(null);
39119
+ const emitNow = useCallback(() => {
39120
+ const el = ref.current;
39121
+ if (!el) return;
39122
+ const html = sanitizeRichHtml(el.innerHTML);
39123
+ onChangeRef.current?.(html);
39124
+ const evt = changeEventRef.current;
39125
+ if (evt) eventBus.emit(`UI:${evt}`, { value: html });
39126
+ }, [eventBus]);
39127
+ const scheduleEmit = useCallback(() => {
39128
+ if (emitTimerRef.current) clearTimeout(emitTimerRef.current);
39129
+ emitTimerRef.current = setTimeout(() => {
39130
+ emitTimerRef.current = null;
39131
+ emitNow();
39132
+ }, CHANGE_DEBOUNCE_MS);
39133
+ }, [emitNow]);
39134
+ const flushEmit = useCallback(() => {
39135
+ if (!emitTimerRef.current) return;
39136
+ clearTimeout(emitTimerRef.current);
39137
+ emitTimerRef.current = null;
39138
+ emitNow();
39139
+ }, [emitNow]);
39140
+ useEffect(() => () => {
39141
+ if (emitTimerRef.current) {
39142
+ clearTimeout(emitTimerRef.current);
39143
+ emitTimerRef.current = null;
39144
+ emitNow();
39145
+ }
39146
+ }, [emitNow]);
39147
+ useEffect(() => {
39148
+ if (readOnly) return;
39149
+ const el = ref.current;
39150
+ if (!el) return;
39151
+ if (document.activeElement === el) return;
39152
+ const next = sanitizeRichHtml(value ?? "") || "<p><br></p>";
39153
+ if (el.innerHTML !== next) {
39154
+ el.innerHTML = next;
39155
+ setEmpty(htmlIsEmpty(el));
39156
+ }
39157
+ }, [readOnly, value]);
39158
+ useEffect(() => {
39159
+ if (readOnly) return;
39160
+ document.execCommand("defaultParagraphSeparator", false, "p");
39161
+ }, [readOnly]);
39162
+ useEffect(() => {
39163
+ if (readOnly || !showToolbar) return;
39164
+ const onSelectionChange = () => {
39165
+ const el = ref.current;
39166
+ if (!el) return;
39167
+ setToolbar(readToolbarState(el));
39305
39168
  };
39306
- return /* @__PURE__ */ jsxs(Box, { className: "space-y-2", children: [
39307
- url ? /* @__PURE__ */ jsx(Box, { as: "img", ...imgProps }) : /* @__PURE__ */ jsxs(
39308
- Box,
39309
- {
39310
- className: cn(
39311
- "flex h-32 items-center justify-center",
39312
- "rounded-md border border-dashed border-border",
39313
- "text-sm text-muted-foreground"
39314
- ),
39315
- children: [
39316
- /* @__PURE__ */ jsx(Icon, { name: "image", className: "mr-2 w-4 h-4" }),
39317
- " ",
39318
- t("richBlockEditor.noImageUrl")
39319
- ]
39320
- }
39321
- ),
39322
- !readOnly && /* @__PURE__ */ jsxs(Box, { className: "flex flex-col gap-2 sm:flex-row", children: [
39323
- /* @__PURE__ */ jsx(
39324
- Input,
39325
- {
39326
- inputType: "url",
39327
- value: url,
39328
- placeholder: "https://example.com/image.png",
39329
- "aria-label": t("richBlockEditor.aria.imageUrl"),
39330
- className: cn(
39331
- "h-8 flex-1 rounded-sm border border-border bg-background",
39332
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
39333
- ),
39334
- onChange: (e) => setMetadata("url", e.target.value)
39335
- }
39336
- ),
39337
- /* @__PURE__ */ jsx(
39338
- Input,
39339
- {
39340
- inputType: "text",
39341
- value: caption,
39342
- placeholder: t("richBlockEditor.placeholder.caption"),
39343
- "aria-label": t("richBlockEditor.aria.imageCaption"),
39344
- className: cn(
39345
- "h-8 flex-1 rounded-sm border border-border bg-background",
39346
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
39347
- ),
39348
- onChange: (e) => setMetadata("caption", e.target.value)
39349
- }
39350
- )
39351
- ] }),
39352
- readOnly && caption && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-center text-muted-foreground", children: caption })
39169
+ document.addEventListener("selectionchange", onSelectionChange);
39170
+ return () => document.removeEventListener("selectionchange", onSelectionChange);
39171
+ }, [readOnly, showToolbar]);
39172
+ const tryInlineMarkdown = useCallback(() => {
39173
+ const root = ref.current;
39174
+ if (!root) return false;
39175
+ const sel = window.getSelection();
39176
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
39177
+ const node = sel.anchorNode;
39178
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
39179
+ if (node.parentElement?.closest("code, pre")) return false;
39180
+ const text = (node.textContent ?? "").slice(0, sel.anchorOffset);
39181
+ for (const rule of MD_INLINE_RULES) {
39182
+ const m = rule.pattern.exec(text);
39183
+ if (!m) continue;
39184
+ const inner = rule.wrap === "em" ? m[2] : m[1];
39185
+ if (!inner || !inner.trim()) continue;
39186
+ const lead = rule.wrap === "em" ? m[1]?.length ?? 0 : 0;
39187
+ const range = document.createRange();
39188
+ range.setStart(node, m.index + lead);
39189
+ range.setEnd(node, sel.anchorOffset);
39190
+ const el = document.createElement(rule.wrap);
39191
+ el.textContent = inner;
39192
+ range.deleteContents();
39193
+ range.insertNode(el);
39194
+ const after = document.createTextNode("\u200B");
39195
+ el.parentNode?.insertBefore(after, el.nextSibling);
39196
+ const caret = document.createRange();
39197
+ caret.setStart(after, 1);
39198
+ caret.collapse(true);
39199
+ sel.removeAllRanges();
39200
+ sel.addRange(caret);
39201
+ return true;
39202
+ }
39203
+ return false;
39204
+ }, []);
39205
+ const tryBlockMarkdown = useCallback(() => {
39206
+ const root = ref.current;
39207
+ if (!root) return false;
39208
+ const sel = window.getSelection();
39209
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
39210
+ const node = sel.anchorNode;
39211
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
39212
+ const block = closestBlock(node, root);
39213
+ if (block ? block.firstChild !== node : root.firstChild !== node) return false;
39214
+ const prefix = (node.textContent ?? "").slice(0, sel.anchorOffset);
39215
+ const rule = MD_BLOCK_PREFIXES[prefix];
39216
+ if (!rule) return false;
39217
+ if (node.parentElement?.closest("pre, code")) return false;
39218
+ if (rule.kind === "list" && node.parentElement?.closest("li")) return false;
39219
+ node.deleteData(0, prefix.length);
39220
+ const caret = document.createRange();
39221
+ caret.setStart(node, 0);
39222
+ caret.collapse(true);
39223
+ sel.removeAllRanges();
39224
+ sel.addRange(caret);
39225
+ if (rule.kind === "list") {
39226
+ const container = block ?? node.parentElement;
39227
+ const list = document.createElement(rule.command === "insertOrderedList" ? "ol" : "ul");
39228
+ const li = document.createElement("li");
39229
+ if (block) {
39230
+ while (block.firstChild) li.appendChild(block.firstChild);
39231
+ list.appendChild(li);
39232
+ block.replaceWith(list);
39233
+ } else if (container) {
39234
+ li.appendChild(node);
39235
+ list.appendChild(li);
39236
+ container.appendChild(list);
39237
+ }
39238
+ if (!li.firstChild) li.appendChild(document.createElement("br"));
39239
+ const liCaret = document.createRange();
39240
+ liCaret.selectNodeContents(li);
39241
+ liCaret.collapse(true);
39242
+ sel.removeAllRanges();
39243
+ sel.addRange(liCaret);
39244
+ } else {
39245
+ document.execCommand("formatBlock", false, rule.tag);
39246
+ }
39247
+ return true;
39248
+ }, []);
39249
+ const afterEdit = useCallback(() => {
39250
+ const el = ref.current;
39251
+ if (!el) return;
39252
+ tryInlineMarkdown();
39253
+ setEmpty(htmlIsEmpty(el));
39254
+ setToolbar(readToolbarState(el));
39255
+ scheduleEmit();
39256
+ }, [scheduleEmit, tryInlineMarkdown]);
39257
+ const handleKeyDown = useCallback((e) => {
39258
+ if (e.key === " " && tryBlockMarkdown()) {
39259
+ e.preventDefault();
39260
+ afterEdit();
39261
+ }
39262
+ }, [afterEdit, tryBlockMarkdown]);
39263
+ const execInline = useCallback((command) => {
39264
+ document.execCommand(command, false);
39265
+ afterEdit();
39266
+ }, [afterEdit]);
39267
+ const execBlock = useCallback((tag) => {
39268
+ const next = toolbar.block === tag ? "p" : tag;
39269
+ document.execCommand("formatBlock", false, next);
39270
+ afterEdit();
39271
+ }, [afterEdit, toolbar.block]);
39272
+ const execList = useCallback((command) => {
39273
+ document.execCommand(command, false);
39274
+ afterEdit();
39275
+ }, [afterEdit]);
39276
+ const execLink = useCallback(() => {
39277
+ if (toolbar.link) {
39278
+ document.execCommand("unlink", false);
39279
+ afterEdit();
39280
+ return;
39281
+ }
39282
+ const url = window.prompt(t("richTextEditor.linkPrompt"));
39283
+ if (!url) return;
39284
+ const safe = safeUrl(url, ["http://", "https://", "mailto:"]) ?? `https://${url}`;
39285
+ document.execCommand("createLink", false, safe);
39286
+ afterEdit();
39287
+ }, [afterEdit, t, toolbar.link]);
39288
+ const execRule = useCallback(() => {
39289
+ document.execCommand("insertHorizontalRule", false);
39290
+ afterEdit();
39291
+ }, [afterEdit]);
39292
+ if (readOnly) {
39293
+ return /* @__PURE__ */ jsxs(Box, { className: cn("almadar-rich-text max-w-none", className), children: [
39294
+ /* @__PURE__ */ jsx(RichTextStyles, {}),
39295
+ /* @__PURE__ */ jsx(Box, { dangerouslySetInnerHTML: { __html: sanitizeRichHtml(value ?? "") } })
39353
39296
  ] });
39354
39297
  }
39355
- case "bullet-list":
39356
- case "numbered-list": {
39357
- const items = block.children ?? [];
39358
- return /* @__PURE__ */ jsxs(
39298
+ return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
39299
+ /* @__PURE__ */ jsx(RichTextStyles, {}),
39300
+ showToolbar && /* @__PURE__ */ jsxs(
39359
39301
  Box,
39360
39302
  {
39361
- as: block.type === "bullet-list" ? "ul" : "ol",
39303
+ role: "toolbar",
39304
+ "aria-label": t("richTextEditor.editorToolbar"),
39362
39305
  className: cn(
39363
- "space-y-1 pl-6",
39364
- block.type === "bullet-list" ? "list-disc" : "list-decimal"
39306
+ "sticky top-0 z-10 flex flex-wrap items-center gap-0.5 self-start",
39307
+ "rounded-md border border-border bg-background/95 px-1 py-0.5 shadow-sm"
39365
39308
  ),
39366
39309
  children: [
39367
- items.map((child) => /* @__PURE__ */ jsxs(Box, { as: "li", className: "group/item flex items-start gap-2", children: [
39368
- /* @__PURE__ */ jsx(
39369
- Editable,
39370
- {
39371
- tag: "span",
39372
- value: child.content ?? "",
39373
- readOnly,
39374
- placeholder: t("richBlockEditor.placeholder.listItem"),
39375
- ariaLabel: t("richBlockEditor.aria.listItem"),
39376
- className: "inline-block min-w-[1ch] flex-1",
39377
- onValueChange: (next) => setChildContent(child.id, next)
39378
- }
39379
- ),
39380
- !readOnly && showAffordances && /* @__PURE__ */ jsx(
39381
- Button,
39382
- {
39383
- type: "button",
39384
- variant: "ghost",
39385
- "aria-label": t("richBlockEditor.aria.removeListItem"),
39386
- className: cn(
39387
- "h-5 w-5 shrink-0 rounded-sm text-muted-foreground p-0 gap-0",
39388
- "opacity-0 group-hover/item:opacity-100 hover:bg-muted hover:text-foreground"
39389
- ),
39390
- onClick: () => removeListItem(child.id),
39391
- children: /* @__PURE__ */ jsx(Icon, { name: "trash", className: "w-3 h-3" })
39392
- }
39393
- )
39394
- ] }, child.id)),
39395
- !readOnly && showAffordances && /* @__PURE__ */ jsx(Box, { as: "li", className: "list-none pl-0", children: /* @__PURE__ */ jsxs(
39396
- Button,
39397
- {
39398
- type: "button",
39399
- variant: "ghost",
39400
- className: cn(
39401
- "inline-flex items-center gap-1 text-xs text-muted-foreground",
39402
- "hover:text-foreground p-0 h-auto"
39403
- ),
39404
- onClick: addListItem,
39405
- children: [
39406
- /* @__PURE__ */ jsx(Icon, { name: "plus", className: "w-3 h-3" }),
39407
- " ",
39408
- t("richBlockEditor.addItem")
39409
- ]
39410
- }
39411
- ) })
39310
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Bold, label: t("richTextEditor.bold"), active: toolbar.bold, onExec: () => execInline("bold") }),
39311
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Italic, label: t("richTextEditor.italic"), active: toolbar.italic, onExec: () => execInline("italic") }),
39312
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Underline, label: t("richTextEditor.underline"), active: toolbar.underline, onExec: () => execInline("underline") }),
39313
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Strikethrough, label: t("richTextEditor.strikethrough"), active: toolbar.strikeThrough, onExec: () => execInline("strikeThrough") }),
39314
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
39315
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading1, label: t("richTextEditor.heading1"), active: toolbar.block === "h1", onExec: () => execBlock("h1") }),
39316
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading2, label: t("richTextEditor.heading2"), active: toolbar.block === "h2", onExec: () => execBlock("h2") }),
39317
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading3, label: t("richTextEditor.heading3"), active: toolbar.block === "h3", onExec: () => execBlock("h3") }),
39318
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
39319
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: List, label: t("richTextEditor.bulletList"), active: toolbar.bullets, onExec: () => execList("insertUnorderedList") }),
39320
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: ListOrdered, label: t("richTextEditor.numberedList"), active: toolbar.numbers, onExec: () => execList("insertOrderedList") }),
39321
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
39322
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
39323
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
39324
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Link$1, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
39325
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Minus, label: t("richTextEditor.divider"), onExec: execRule })
39412
39326
  ]
39413
39327
  }
39414
- );
39415
- }
39416
- case "paragraph":
39417
- default:
39418
- return /* @__PURE__ */ jsx(
39419
- Editable,
39328
+ ),
39329
+ /* @__PURE__ */ jsx(
39330
+ Box,
39420
39331
  {
39421
- tag: "p",
39422
- value: block.content ?? "",
39423
- readOnly,
39424
- placeholder: placeholder ?? t("richBlockEditor.placeholder.paragraph"),
39425
- ariaLabel: t("richBlockEditor.aria.paragraphBlock"),
39426
- className: "leading-7",
39427
- onValueChange: setContent
39332
+ ref,
39333
+ contentEditable: true,
39334
+ suppressContentEditableWarning: true,
39335
+ role: "textbox",
39336
+ "aria-multiline": "true",
39337
+ "aria-label": t("richTextEditor.editorSurface"),
39338
+ "data-placeholder": placeholder ?? t("richTextEditor.placeholder"),
39339
+ "data-empty": empty ? "true" : "false",
39340
+ className: cn(
39341
+ "almadar-rich-text max-w-none min-h-[8rem] outline-none",
39342
+ "data-[empty=true]:before:content-[attr(data-placeholder)]",
39343
+ "data-[empty=true]:before:text-muted-foreground/60",
39344
+ "data-[empty=true]:before:pointer-events-none",
39345
+ "data-[empty=true]:before:absolute",
39346
+ "relative cursor-text"
39347
+ ),
39348
+ onInput: afterEdit,
39349
+ onKeyDown: handleKeyDown,
39350
+ onBlur: flushEmit
39428
39351
  }
39429
- );
39430
- }
39352
+ )
39353
+ ] });
39354
+ };
39355
+ RichTextEditor.displayName = "RichTextEditor";
39356
+ }
39357
+ });
39358
+ function renderIconInput4(icon, props) {
39359
+ return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
39360
+ }
39361
+ function DocumentPanel({
39362
+ entity,
39363
+ title,
39364
+ subtitle,
39365
+ value,
39366
+ actions,
39367
+ maxInlineActions,
39368
+ editing = false,
39369
+ titleCommitEvent,
39370
+ contentChangeEvent,
39371
+ editEvent,
39372
+ doneEvent,
39373
+ editLabel,
39374
+ doneLabel,
39375
+ autosaveHint,
39376
+ placeholder,
39377
+ className
39378
+ }) {
39379
+ const eventBus = useEventBus();
39380
+ const { t } = useTranslate();
39381
+ const [titleDraft, setTitleDraft] = useState(null);
39382
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
39383
+ const actionDefs = actions ?? [];
39384
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
39385
+ const inlineActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
39386
+ const menuActions = actionDefs.filter((a) => !inlineActions.includes(a));
39387
+ const fireAction = (action) => {
39388
+ if (!action.event) return;
39389
+ eventBus.emit(`UI:${action.event}`, { id: recordId, row: entity });
39431
39390
  };
39432
- return /* @__PURE__ */ jsxs(
39391
+ const commitTitle = () => {
39392
+ if (!titleCommitEvent) return;
39393
+ const next = (titleDraft ?? "").trim();
39394
+ setTitleDraft(null);
39395
+ if (!next || next === title) return;
39396
+ eventBus.emit(`UI:${titleCommitEvent}`, { title: next, id: recordId });
39397
+ };
39398
+ const untitled = t("documentPanel.untitled") || "Untitled";
39399
+ const titleNode = titleCommitEvent && titleDraft !== null ? /* @__PURE__ */ jsx(
39400
+ Input,
39401
+ {
39402
+ value: titleDraft,
39403
+ autoFocus: true,
39404
+ "aria-label": t("common.title"),
39405
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
39406
+ onFocus: (e) => e.currentTarget.select(),
39407
+ onChange: (e) => setTitleDraft(e.target.value),
39408
+ onBlur: commitTitle,
39409
+ onKeyDown: (e) => {
39410
+ if (e.key === "Enter") {
39411
+ e.preventDefault();
39412
+ commitTitle();
39413
+ } else if (e.key === "Escape") {
39414
+ e.preventDefault();
39415
+ setTitleDraft(null);
39416
+ }
39417
+ },
39418
+ "data-testid": "document-title-input"
39419
+ }
39420
+ ) : titleCommitEvent ? /* @__PURE__ */ jsx(
39433
39421
  Box,
39434
39422
  {
39435
- className: cn(
39436
- "group relative flex items-start gap-2 rounded-sm",
39437
- "px-2 py-1 hover:bg-muted/30"
39438
- ),
39439
- "data-block-id": block.id,
39440
- "data-block-type": block.type,
39441
- children: [
39442
- !readOnly && showAffordances && /* @__PURE__ */ jsx(Box, { className: "flex w-8 shrink-0 items-center pt-1", children: /* @__PURE__ */ jsx(
39443
- BlockMenu,
39423
+ role: "button",
39424
+ tabIndex: 0,
39425
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
39426
+ onClick: () => setTitleDraft(title ?? ""),
39427
+ onKeyDown: (e) => {
39428
+ if (e.key === "Enter" || e.key === " ") {
39429
+ e.preventDefault();
39430
+ setTitleDraft(title ?? "");
39431
+ }
39432
+ },
39433
+ "data-testid": "document-title-editable",
39434
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", className: cn(!title && "text-muted-foreground"), children: title || untitled })
39435
+ }
39436
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || untitled });
39437
+ const emitWithId = (event) => () => {
39438
+ if (event) eventBus.emit(`UI:${event}`, { id: recordId });
39439
+ };
39440
+ return /* @__PURE__ */ jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
39441
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
39442
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
39443
+ titleNode,
39444
+ subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
39445
+ ] }),
39446
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
39447
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
39448
+ /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
39449
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
39450
+ editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
39451
+ /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
39452
+ editLabel ?? (t("documentPanel.edit") || "Edit")
39453
+ ] }),
39454
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
39455
+ Button,
39444
39456
  {
39445
- block,
39446
- readOnly,
39447
- onDelete,
39448
- onDuplicate,
39449
- onChangeType
39457
+ variant: "primary",
39458
+ size: "sm",
39459
+ onClick: () => fireAction(action),
39460
+ "data-testid": `action-${action.event}`,
39461
+ children: [
39462
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
39463
+ action.label
39464
+ ]
39465
+ },
39466
+ idx
39467
+ )),
39468
+ menuActions.length > 0 && /* @__PURE__ */ jsx(
39469
+ Menu,
39470
+ {
39471
+ position: "bottom-end",
39472
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
39473
+ items: menuActions.map((action) => ({
39474
+ label: action.label,
39475
+ icon: action.icon,
39476
+ variant: action.variant === "danger" ? "danger" : "default",
39477
+ onClick: () => fireAction(action)
39478
+ }))
39450
39479
  }
39451
- ) }),
39452
- /* @__PURE__ */ jsx(Box, { className: "min-w-0 flex-1", children: renderBody() })
39453
- ]
39454
- }
39455
- );
39480
+ )
39481
+ ] }) })
39482
+ ] }),
39483
+ editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
39484
+ Box,
39485
+ {
39486
+ onClick: emitWithId(editEvent),
39487
+ className: cn(
39488
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
39489
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
39490
+ ),
39491
+ "data-testid": "document-body",
39492
+ children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
39493
+ }
39494
+ )
39495
+ ] }) });
39456
39496
  }
39457
- var TOOLBAR_ENTRIES, BLOCK_TYPE_LABEL_KEY, CHANGEABLE_TYPES, _idSeq, BLOCK_TYPES, RichBlockEditor;
39458
- var init_RichBlockEditor = __esm({
39459
- "components/core/molecules/RichBlockEditor.tsx"() {
39497
+ var init_DocumentPanel = __esm({
39498
+ "components/core/molecules/DocumentPanel.tsx"() {
39460
39499
  "use client";
39461
39500
  init_cn();
39462
- init_Card();
39501
+ init_useEventBus();
39502
+ init_Box();
39503
+ init_Stack();
39463
39504
  init_Typography();
39464
39505
  init_Button();
39465
- init_Box();
39466
- init_Divider();
39467
- init_Input();
39506
+ init_Card();
39468
39507
  init_Icon();
39469
- init_useEventBus();
39470
- TOOLBAR_ENTRIES = [
39471
- { type: "paragraph", labelKey: "richBlockEditor.toolbar.text", icon: Type },
39472
- { type: "heading-1", labelKey: "richBlockEditor.toolbar.h1", icon: Heading1 },
39473
- { type: "heading-2", labelKey: "richBlockEditor.toolbar.h2", icon: Heading2 },
39474
- { type: "heading-3", labelKey: "richBlockEditor.toolbar.h3", icon: Heading3 },
39475
- { type: "bullet-list", labelKey: "richBlockEditor.toolbar.bulletList", icon: List },
39476
- { type: "numbered-list", labelKey: "richBlockEditor.toolbar.numbered", icon: ListOrdered },
39477
- { type: "quote", labelKey: "richBlockEditor.toolbar.quote", icon: Quote },
39478
- { type: "code", labelKey: "richBlockEditor.toolbar.code", icon: Code },
39479
- { type: "divider", labelKey: "richBlockEditor.toolbar.divider", icon: Minus },
39480
- { type: "image", labelKey: "richBlockEditor.toolbar.image", icon: Image$1 }
39481
- ];
39482
- BLOCK_TYPE_LABEL_KEY = {
39483
- paragraph: "richBlockEditor.blockType.paragraph",
39484
- "heading-1": "richBlockEditor.blockType.heading1",
39485
- "heading-2": "richBlockEditor.blockType.heading2",
39486
- "heading-3": "richBlockEditor.blockType.heading3",
39487
- "bullet-list": "richBlockEditor.blockType.bulletList",
39488
- "numbered-list": "richBlockEditor.blockType.numberedList",
39489
- quote: "richBlockEditor.blockType.quote",
39490
- code: "richBlockEditor.blockType.code",
39491
- divider: "richBlockEditor.blockType.divider",
39492
- image: "richBlockEditor.blockType.image"
39493
- };
39494
- CHANGEABLE_TYPES = [
39495
- "paragraph",
39496
- "heading-1",
39497
- "heading-2",
39498
- "heading-3",
39499
- "bullet-list",
39500
- "numbered-list",
39501
- "quote",
39502
- "code"
39503
- ];
39504
- _idSeq = 0;
39505
- BLOCK_TYPES = /* @__PURE__ */ new Set([
39506
- "paragraph",
39507
- "heading-1",
39508
- "heading-2",
39509
- "heading-3",
39510
- "bullet-list",
39511
- "numbered-list",
39512
- "quote",
39513
- "code",
39514
- "divider",
39515
- "image"
39516
- ]);
39517
- RichBlockEditor = ({
39518
- initialBlocks,
39519
- onChange,
39520
- changeEvent,
39521
- readOnly = false,
39522
- placeholder,
39523
- enableBlocks = true,
39524
- showToolbar = true,
39525
- className
39526
- }) => {
39527
- const { t } = useTranslate();
39528
- const [blocks, setBlocks] = useState(
39529
- () => normalizeBlocks(initialBlocks)
39530
- );
39531
- const onChangeRef = useRef(onChange);
39532
- useEffect(() => {
39533
- onChangeRef.current = onChange;
39534
- }, [onChange]);
39535
- const eventBus = useEventBus();
39536
- const changeEventRef = useRef(changeEvent);
39537
- useEffect(() => {
39538
- changeEventRef.current = changeEvent;
39539
- }, [changeEvent]);
39540
- const commit = useCallback((next) => {
39541
- setBlocks(next);
39542
- onChangeRef.current?.(next);
39543
- const evt = changeEventRef.current;
39544
- if (evt) eventBus.emit(`UI:${evt}`, { blocks: next });
39545
- }, [eventBus]);
39546
- const handleAppend = useCallback(
39547
- (type) => {
39548
- if (readOnly) return;
39549
- commit([...blocks, createBlock(type)]);
39550
- },
39551
- [blocks, commit, readOnly]
39552
- );
39553
- const handleUpdate = useCallback(
39554
- (id, updater) => {
39555
- commit(replaceBlock(blocks, id, updater));
39556
- },
39557
- [blocks, commit]
39558
- );
39559
- const handleDelete = useCallback(
39560
- (id) => {
39561
- const next = removeBlock(blocks, id);
39562
- commit(next.length > 0 ? next : [createBlock("paragraph")]);
39563
- },
39564
- [blocks, commit]
39565
- );
39566
- const handleDuplicate = useCallback(
39567
- (id) => {
39568
- const target = blocks.find((b) => b.id === id);
39569
- if (!target) return;
39570
- commit(insertAfter(blocks, id, duplicateBlock(target)));
39571
- },
39572
- [blocks, commit]
39508
+ init_Input();
39509
+ init_Menu();
39510
+ init_RichTextEditor();
39511
+ }
39512
+ });
39513
+ function renderIconInput5(icon, props) {
39514
+ return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
39515
+ }
39516
+ function fieldName(field) {
39517
+ return field.name ?? field.key ?? "";
39518
+ }
39519
+ function relationLabel(value) {
39520
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
39521
+ for (const key of ["name", "title", "label"]) {
39522
+ const candidate = value[key];
39523
+ if (typeof candidate === "string" && candidate !== "") return candidate;
39524
+ }
39525
+ const id = value.id;
39526
+ return id !== void 0 && id !== null ? String(id) : null;
39527
+ }
39528
+ function fieldKind(field, value) {
39529
+ if (field.kind) return field.kind;
39530
+ if (field.options && field.options.length > 0) return "select";
39531
+ if (typeof value === "boolean" || field.format === "boolean") return "boolean";
39532
+ if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
39533
+ return "text";
39534
+ }
39535
+ function DocumentDetails({
39536
+ entity,
39537
+ fields,
39538
+ metaCommitEvent,
39539
+ title,
39540
+ className
39541
+ }) {
39542
+ const eventBus = useEventBus();
39543
+ const { t } = useTranslate();
39544
+ const [fieldDraft, setFieldDraft] = useState(null);
39545
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
39546
+ const fieldDefs = (fields ?? []).filter((f3) => fieldName(f3) !== "");
39547
+ if (fieldDefs.length === 0) return null;
39548
+ const commitField = (name, next, current) => {
39549
+ setFieldDraft(null);
39550
+ if (!metaCommitEvent || next === current) return;
39551
+ eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
39552
+ };
39553
+ const renderValue = (field) => {
39554
+ const name = fieldName(field);
39555
+ const raw = getNestedValue(entity ?? {}, name);
39556
+ const kind = fieldKind(field, raw);
39557
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
39558
+ if (kind === "boolean") {
39559
+ return /* @__PURE__ */ jsx(
39560
+ Switch,
39561
+ {
39562
+ checked: Boolean(raw),
39563
+ disabled: !metaCommitEvent,
39564
+ "aria-label": label,
39565
+ onChange: (checked) => commitField(name, checked, Boolean(raw))
39566
+ }
39573
39567
  );
39574
- const handleChangeType = useCallback(
39575
- (id, type) => {
39576
- commit(
39577
- replaceBlock(blocks, id, (b) => changeBlockType(b, type))
39578
- );
39579
- },
39580
- [blocks, commit]
39568
+ }
39569
+ if (kind === "select") {
39570
+ return /* @__PURE__ */ jsx(
39571
+ Select,
39572
+ {
39573
+ value: raw !== void 0 && raw !== null ? String(raw) : "",
39574
+ disabled: !metaCommitEvent,
39575
+ "aria-label": label,
39576
+ className: "h-8 w-full",
39577
+ options: (field.options ?? []).map((opt) => ({ value: opt, label: humanizeEnumValue(opt) })),
39578
+ onChange: (e) => commitField(name, e.target.value, raw)
39579
+ }
39581
39580
  );
39582
- return /* @__PURE__ */ jsxs(
39583
- Card,
39581
+ }
39582
+ if (kind === "readonly" || !metaCommitEvent) {
39583
+ if (Array.isArray(raw)) {
39584
+ if (raw.length === 0) {
39585
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
39586
+ }
39587
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
39588
+ }
39589
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
39590
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
39591
+ }
39592
+ if (fieldDraft?.name === name) {
39593
+ return /* @__PURE__ */ jsx(
39594
+ Input,
39584
39595
  {
39585
- variant: "bordered",
39586
- padding: "none",
39587
- className: cn("flex flex-col text-card-foreground", className),
39588
- children: [
39589
- enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsx(
39590
- Box,
39591
- {
39592
- role: "toolbar",
39593
- "aria-label": t("richBlockEditor.editorToolbar"),
39594
- className: cn(
39595
- "flex flex-wrap items-center gap-1",
39596
- "border-b border-border bg-muted/30 px-2 py-2"
39597
- ),
39598
- children: TOOLBAR_ENTRIES.map((entry) => {
39599
- const Icon2 = entry.icon;
39600
- const entryLabel = t(entry.labelKey);
39601
- return /* @__PURE__ */ jsxs(
39602
- Button,
39603
- {
39604
- type: "button",
39605
- variant: "ghost",
39606
- size: "sm",
39607
- "aria-label": t("richBlockEditor.insertEntry", { label: entryLabel }),
39608
- title: entryLabel,
39609
- onClick: () => handleAppend(entry.type),
39610
- children: [
39611
- /* @__PURE__ */ jsx(Icon2, { size: 14 }),
39612
- /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "ml-1 hidden text-xs sm:inline", children: entryLabel })
39613
- ]
39614
- },
39615
- entry.type
39616
- );
39617
- })
39618
- }
39619
- ),
39620
- /* @__PURE__ */ jsx(Box, { className: "flex flex-col gap-1 px-3 py-3", children: blocks.map((block) => /* @__PURE__ */ jsx(
39621
- BlockRow,
39622
- {
39623
- block,
39624
- readOnly,
39625
- showAffordances: enableBlocks,
39626
- placeholder,
39627
- onUpdate: (updater) => handleUpdate(block.id, updater),
39628
- onDelete: () => handleDelete(block.id),
39629
- onDuplicate: () => handleDuplicate(block.id),
39630
- onChangeType: (type) => handleChangeType(block.id, type)
39631
- },
39632
- block.id
39633
- )) })
39634
- ]
39596
+ value: fieldDraft.value,
39597
+ autoFocus: true,
39598
+ "aria-label": label,
39599
+ className: "h-8 w-full",
39600
+ onChange: (e) => setFieldDraft({ name, value: e.target.value }),
39601
+ onBlur: () => commitField(name, fieldDraft.value.trim(), raw),
39602
+ onKeyDown: (e) => {
39603
+ if (e.key === "Enter") {
39604
+ e.preventDefault();
39605
+ commitField(name, fieldDraft.value.trim(), raw);
39606
+ } else if (e.key === "Escape") {
39607
+ e.preventDefault();
39608
+ setFieldDraft(null);
39609
+ }
39610
+ },
39611
+ "data-testid": `document-property-input-${name}`
39635
39612
  }
39636
39613
  );
39637
- };
39638
- RichBlockEditor.displayName = "RichBlockEditor";
39614
+ }
39615
+ const shown = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
39616
+ return /* @__PURE__ */ jsx(
39617
+ Box,
39618
+ {
39619
+ role: "button",
39620
+ tabIndex: 0,
39621
+ className: cn(
39622
+ "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
39623
+ (raw === void 0 || raw === null || raw === "") && "text-muted-foreground"
39624
+ ),
39625
+ onClick: () => setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" }),
39626
+ onKeyDown: (e) => {
39627
+ if (e.key === "Enter" || e.key === " ") {
39628
+ e.preventDefault();
39629
+ setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" });
39630
+ }
39631
+ },
39632
+ "data-testid": `document-property-${name}`,
39633
+ children: /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown })
39634
+ }
39635
+ );
39636
+ };
39637
+ return /* @__PURE__ */ jsx(Card, { variant: "bordered", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-4", children: [
39638
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", weight: "medium", className: "uppercase tracking-wide", children: title ?? (t("documentDetails.title") || "Details") }),
39639
+ /* @__PURE__ */ jsx(VStack, { gap: "sm", children: fieldDefs.map((field) => {
39640
+ const name = fieldName(field);
39641
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
39642
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
39643
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
39644
+ field.icon && renderIconInput5(field.icon, { size: "xs", className: "text-muted-foreground" }),
39645
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: label })
39646
+ ] }),
39647
+ /* @__PURE__ */ jsx(Box, { className: "min-w-0", children: renderValue(field) })
39648
+ ] }, name);
39649
+ }) })
39650
+ ] }) });
39651
+ }
39652
+ var init_DocumentDetails = __esm({
39653
+ "components/core/molecules/DocumentDetails.tsx"() {
39654
+ "use client";
39655
+ init_cn();
39656
+ init_format();
39657
+ init_getNestedValue();
39658
+ init_useEventBus();
39659
+ init_Box();
39660
+ init_Stack();
39661
+ init_Typography();
39662
+ init_Badge();
39663
+ init_Card();
39664
+ init_Icon();
39665
+ init_Input();
39666
+ init_Select();
39667
+ init_Switch();
39639
39668
  }
39640
39669
  });
39641
39670
  function collectInitiallyCollapsed(nodes, acc) {
@@ -44175,8 +44204,8 @@ var init_DataTable = __esm({
44175
44204
  DataTable.displayName = "DataTable";
44176
44205
  }
44177
44206
  });
44178
- function getBadgeVariant(fieldName, value) {
44179
- const name = fieldName.toLowerCase();
44207
+ function getBadgeVariant(fieldName2, value) {
44208
+ const name = fieldName2.toLowerCase();
44180
44209
  const val = String(value).toLowerCase();
44181
44210
  if (name.includes("status")) {
44182
44211
  if (val.includes("complete") || val.includes("done") || val.includes("active"))
@@ -44192,12 +44221,12 @@ function getBadgeVariant(fieldName, value) {
44192
44221
  }
44193
44222
  return "default";
44194
44223
  }
44195
- function formatFieldValue2(value, fieldName) {
44224
+ function formatFieldValue2(value, fieldName2) {
44196
44225
  if (typeof value === "number") {
44197
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
44226
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
44198
44227
  return `${value}%`;
44199
44228
  }
44200
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
44229
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
44201
44230
  return `$${value.toLocaleString()}`;
44202
44231
  }
44203
44232
  return value.toLocaleString();
@@ -44207,7 +44236,7 @@ function formatFieldValue2(value, fieldName) {
44207
44236
  }
44208
44237
  return String(value);
44209
44238
  }
44210
- function renderRichFieldValue(value, fieldName, fieldType, meta) {
44239
+ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
44211
44240
  if (value === void 0 || value === null) return "\u2014";
44212
44241
  const str = String(value);
44213
44242
  switch (fieldType) {
@@ -44218,7 +44247,7 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
44218
44247
  "img",
44219
44248
  {
44220
44249
  src: str,
44221
- alt: formatFieldLabel(fieldName),
44250
+ alt: formatFieldLabel(fieldName2),
44222
44251
  className: "max-w-full max-h-64 rounded-md object-contain",
44223
44252
  loading: "lazy"
44224
44253
  }
@@ -44353,9 +44382,9 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
44353
44382
  return /* @__PURE__ */ jsx("a", { href: `tel:${str}`, className: "text-primary hover:underline", children: str });
44354
44383
  default:
44355
44384
  if (meta?.values && meta.values.length > 0 && meta.values.includes(str)) {
44356
- return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName, str), children: humanizeEnumValue(str) });
44385
+ return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName2, str), children: humanizeEnumValue(str) });
44357
44386
  }
44358
- return formatFieldValue2(value, fieldName);
44387
+ return formatFieldValue2(value, fieldName2);
44359
44388
  }
44360
44389
  }
44361
44390
  function normalizeFieldDefs(fields) {
@@ -44825,16 +44854,11 @@ var init_DetailPanel = __esm({
44825
44854
  footer
44826
44855
  ] })
44827
44856
  ] }) });
44828
- return /* @__PURE__ */ jsx(
44829
- Box,
44830
- {
44831
- className: cn(
44832
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
44833
- className
44834
- ),
44835
- children: content
44836
- }
44837
- );
44857
+ if (!slideOver) {
44858
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
44859
+ }
44860
+ const panel = /* @__PURE__ */ jsx(Box, { className: cn("fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6", className), children: content });
44861
+ return typeof document === "undefined" ? panel : createPortal(panel, document.body);
44838
44862
  };
44839
44863
  DetailPanel.displayName = "DetailPanel";
44840
44864
  }
@@ -45203,7 +45227,21 @@ var init_Form = __esm({
45203
45227
  const normalizedInitialData = React94__default.useMemo(() => {
45204
45228
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
45205
45229
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
45206
- return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
45230
+ const merged = entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
45231
+ const toId = (value) => {
45232
+ if (value === null || value === void 0 || typeof value !== "object" || value instanceof Date) return value;
45233
+ if (Array.isArray(value)) {
45234
+ return value.map(
45235
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && item.id !== void 0 && item.id !== null ? String(item.id) : item
45236
+ );
45237
+ }
45238
+ return value.id !== void 0 && value.id !== null ? String(value.id) : value;
45239
+ };
45240
+ const normalized = {};
45241
+ for (const [key, value] of Object.entries(merged)) {
45242
+ normalized[key] = key === "id" ? value : toId(value);
45243
+ }
45244
+ return normalized;
45207
45245
  }, [entity, initialData]);
45208
45246
  const entityDerivedFields = React94__default.useMemo(() => {
45209
45247
  if (fields && fields.length > 0) return void 0;
@@ -45332,8 +45370,8 @@ var init_Form = __esm({
45332
45370
  checkViolations(name, newFormData);
45333
45371
  };
45334
45372
  const isFieldVisible = React94__default.useCallback(
45335
- (fieldName) => {
45336
- const condition = conditionalFields[fieldName];
45373
+ (fieldName2) => {
45374
+ const condition = conditionalFields[fieldName2];
45337
45375
  if (!condition) return true;
45338
45376
  return Boolean(evaluateFormExpression(condition, evalContext));
45339
45377
  },
@@ -45379,9 +45417,9 @@ var init_Form = __esm({
45379
45417
  };
45380
45418
  const handleInvalid = (e) => {
45381
45419
  const target = e.target;
45382
- const fieldName = target.getAttribute("data-field-name") ?? target.name ?? "";
45420
+ const fieldName2 = target.getAttribute("data-field-name") ?? target.name ?? "";
45383
45421
  const fieldMessage = target.validationMessage || "Invalid value";
45384
- debug("forms", "invalid", { mode: formMode, fieldName, fieldMessage });
45422
+ debug("forms", "invalid", { mode: formMode, fieldName: fieldName2, fieldMessage });
45385
45423
  queueMicrotask(() => {
45386
45424
  const form = formRef.current;
45387
45425
  if (!form) return;
@@ -45417,22 +45455,22 @@ var init_Form = __esm({
45417
45455
  };
45418
45456
  const renderField = React94__default.useCallback(
45419
45457
  (field) => {
45420
- const fieldName = field.name || field.field;
45421
- if (!fieldName) return null;
45422
- if (!isFieldVisible(fieldName)) {
45458
+ const fieldName2 = field.name || field.field;
45459
+ if (!fieldName2) return null;
45460
+ if (!isFieldVisible(fieldName2)) {
45423
45461
  return null;
45424
45462
  }
45425
45463
  const inputType = determineInputType(field);
45426
- const label = field.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, " $1");
45427
- const currentValue = formData[fieldName] ?? field.defaultValue ?? "";
45428
- return /* @__PURE__ */ jsxs(VStack, { gap: "xs", "data-field": fieldName, children: [
45464
+ const label = field.label || fieldName2.charAt(0).toUpperCase() + fieldName2.slice(1).replace(/([A-Z])/g, " $1");
45465
+ const currentValue = formData[fieldName2] ?? field.defaultValue ?? "";
45466
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", "data-field": fieldName2, children: [
45429
45467
  inputType !== "checkbox" && /* @__PURE__ */ jsxs(Typography, { as: "label", variant: "label", weight: "bold", children: [
45430
45468
  label,
45431
45469
  field.required && /* @__PURE__ */ jsx(Typography, { as: "span", color: "error", className: "ml-1", children: "*" })
45432
45470
  ] }),
45433
- renderFieldInput(field, fieldName, inputType, currentValue, label),
45471
+ renderFieldInput(field, fieldName2, inputType, currentValue, label),
45434
45472
  field.hint && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: field.hint })
45435
- ] }, fieldName);
45473
+ ] }, fieldName2);
45436
45474
  },
45437
45475
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
45438
45476
  );
@@ -45461,8 +45499,8 @@ var init_Form = __esm({
45461
45499
  }
45462
45500
  return field;
45463
45501
  }).map((field) => {
45464
- const fieldName = field.name || field.field;
45465
- const override = fieldOverrides?.find((o) => o.name === fieldName);
45502
+ const fieldName2 = field.name || field.field;
45503
+ const override = fieldOverrides?.find((o) => o.name === fieldName2);
45466
45504
  if (!override) return field;
45467
45505
  return {
45468
45506
  ...field,
@@ -45518,11 +45556,11 @@ var init_Form = __esm({
45518
45556
  ] }, section.id);
45519
45557
  }).filter(Boolean);
45520
45558
  }, [sections, isSectionVisible, collapsedSections, renderField, gap]);
45521
- function renderFieldInput(field, fieldName, inputType, currentValue, label) {
45559
+ function renderFieldInput(field, fieldName2, inputType, currentValue, label) {
45522
45560
  const commonProps = {
45523
- id: fieldName,
45524
- name: fieldName,
45525
- "data-field-name": fieldName,
45561
+ id: fieldName2,
45562
+ name: fieldName2,
45563
+ "data-field-name": fieldName2,
45526
45564
  required: field.required,
45527
45565
  disabled: isLoading,
45528
45566
  placeholder: field.placeholder,
@@ -45536,7 +45574,7 @@ var init_Form = __esm({
45536
45574
  ...commonProps,
45537
45575
  label: label + (field.required ? " *" : ""),
45538
45576
  checked: Boolean(currentValue),
45539
- onChange: (e) => handleChange(fieldName, e.target.checked)
45577
+ onChange: (e) => handleChange(fieldName2, e.target.checked)
45540
45578
  }
45541
45579
  );
45542
45580
  case "textarea":
@@ -45545,7 +45583,7 @@ var init_Form = __esm({
45545
45583
  {
45546
45584
  ...commonProps,
45547
45585
  value: String(currentValue),
45548
- onChange: (e) => handleChange(fieldName, e.target.value),
45586
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45549
45587
  minLength: field.min,
45550
45588
  maxLength: field.max
45551
45589
  }
@@ -45558,14 +45596,14 @@ var init_Form = __esm({
45558
45596
  ...commonProps,
45559
45597
  options,
45560
45598
  value: String(currentValue),
45561
- onValueChange: (v) => handleChange(fieldName, v),
45599
+ onValueChange: (v) => handleChange(fieldName2, v),
45562
45600
  placeholder: field.placeholder || `Select ${label}...`
45563
45601
  }
45564
45602
  );
45565
45603
  }
45566
45604
  case "relation": {
45567
- const relationOptions = relationsData[fieldName] || [];
45568
- const relationLoading = relationsLoading[fieldName] || false;
45605
+ const relationOptions = relationsData[fieldName2] || [];
45606
+ const relationLoading = relationsLoading[fieldName2] || false;
45569
45607
  if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
45570
45608
  const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
45571
45609
  return /* @__PURE__ */ jsx(
@@ -45577,7 +45615,7 @@ var init_Form = __esm({
45577
45615
  clearable: true,
45578
45616
  options: [...relationOptions],
45579
45617
  value: selectedValues,
45580
- onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
45618
+ onValueChange: (value) => handleChange(fieldName2, Array.isArray(value) ? value : [value]),
45581
45619
  placeholder: field.placeholder || `Select ${label}...`
45582
45620
  }
45583
45621
  );
@@ -45587,7 +45625,7 @@ var init_Form = __esm({
45587
45625
  {
45588
45626
  ...commonProps,
45589
45627
  value: currentValue ? String(currentValue) : void 0,
45590
- onChange: (value) => handleChange(fieldName, value),
45628
+ onChange: (value) => handleChange(fieldName2, value),
45591
45629
  options: relationOptions,
45592
45630
  isLoading: relationLoading,
45593
45631
  placeholder: field.placeholder || `Select ${label}...`,
@@ -45604,7 +45642,7 @@ var init_Form = __esm({
45604
45642
  placeholder: field.placeholder,
45605
45643
  disabled: isLoading,
45606
45644
  value: arrayValue,
45607
- onChange: (next) => handleChange(fieldName, [...next])
45645
+ onChange: (next) => handleChange(fieldName2, [...next])
45608
45646
  }
45609
45647
  );
45610
45648
  }
@@ -45616,7 +45654,7 @@ var init_Form = __esm({
45616
45654
  type: "number",
45617
45655
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
45618
45656
  onChange: (e) => handleChange(
45619
- fieldName,
45657
+ fieldName2,
45620
45658
  e.target.value ? Number(e.target.value) : void 0
45621
45659
  ),
45622
45660
  min: field.min,
@@ -45633,7 +45671,7 @@ var init_Form = __esm({
45633
45671
  icon: DollarSign,
45634
45672
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
45635
45673
  onChange: (e) => handleChange(
45636
- fieldName,
45674
+ fieldName2,
45637
45675
  e.target.value ? Number(e.target.value) : void 0
45638
45676
  ),
45639
45677
  min: field.min,
@@ -45651,7 +45689,7 @@ var init_Form = __esm({
45651
45689
  const f3 = files[0];
45652
45690
  if (!f3) return;
45653
45691
  const reader = new FileReader();
45654
- reader.onload = () => handleChange(fieldName, {
45692
+ reader.onload = () => handleChange(fieldName2, {
45655
45693
  name: f3.name,
45656
45694
  mimeType: f3.type,
45657
45695
  sizeBytes: f3.size,
@@ -45668,7 +45706,7 @@ var init_Form = __esm({
45668
45706
  ...commonProps,
45669
45707
  type: "date",
45670
45708
  value: formatDateValue(currentValue),
45671
- onChange: (e) => handleChange(fieldName, e.target.value)
45709
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45672
45710
  }
45673
45711
  );
45674
45712
  case "datetime-local":
@@ -45678,7 +45716,7 @@ var init_Form = __esm({
45678
45716
  ...commonProps,
45679
45717
  type: "datetime-local",
45680
45718
  value: formatDateTimeValue(currentValue),
45681
- onChange: (e) => handleChange(fieldName, e.target.value)
45719
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45682
45720
  }
45683
45721
  );
45684
45722
  case "email":
@@ -45688,7 +45726,7 @@ var init_Form = __esm({
45688
45726
  ...commonProps,
45689
45727
  type: "email",
45690
45728
  value: String(currentValue),
45691
- onChange: (e) => handleChange(fieldName, e.target.value),
45729
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45692
45730
  minLength: field.min,
45693
45731
  maxLength: field.max
45694
45732
  }
@@ -45700,7 +45738,7 @@ var init_Form = __esm({
45700
45738
  ...commonProps,
45701
45739
  type: "url",
45702
45740
  value: String(currentValue),
45703
- onChange: (e) => handleChange(fieldName, e.target.value),
45741
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45704
45742
  minLength: field.min,
45705
45743
  maxLength: field.max
45706
45744
  }
@@ -45712,7 +45750,7 @@ var init_Form = __esm({
45712
45750
  ...commonProps,
45713
45751
  type: "password",
45714
45752
  value: String(currentValue),
45715
- onChange: (e) => handleChange(fieldName, e.target.value),
45753
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45716
45754
  minLength: field.min,
45717
45755
  maxLength: field.max
45718
45756
  }
@@ -45725,7 +45763,7 @@ var init_Form = __esm({
45725
45763
  ...commonProps,
45726
45764
  type: "text",
45727
45765
  value: String(currentValue),
45728
- onChange: (e) => handleChange(fieldName, e.target.value),
45766
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45729
45767
  minLength: field.min,
45730
45768
  maxLength: field.max
45731
45769
  }
@@ -46082,7 +46120,7 @@ function entityFieldsFromListItem(item) {
46082
46120
  }
46083
46121
  return result;
46084
46122
  }
46085
- function getStatusStyle(fieldName, value) {
46123
+ function getStatusStyle(fieldName2, value) {
46086
46124
  const val = String(value).toLowerCase();
46087
46125
  if (val.includes("complete") || val.includes("done"))
46088
46126
  return STATUS_STYLES.complete;
@@ -46098,12 +46136,12 @@ function getStatusStyle(fieldName, value) {
46098
46136
  if (val.includes("low")) return STATUS_STYLES.low;
46099
46137
  return STATUS_STYLES.default;
46100
46138
  }
46101
- function formatValue3(value, fieldName) {
46139
+ function formatValue3(value, fieldName2) {
46102
46140
  if (typeof value === "number") {
46103
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
46141
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
46104
46142
  return `${value}%`;
46105
46143
  }
46106
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
46144
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
46107
46145
  return new Intl.NumberFormat("en-US", {
46108
46146
  style: "currency",
46109
46147
  currency: "USD",
@@ -46126,8 +46164,8 @@ function formatValue3(value, fieldName) {
46126
46164
  }
46127
46165
  return String(value);
46128
46166
  }
46129
- function formatFieldLabel2(fieldName) {
46130
- return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
46167
+ function formatFieldLabel2(fieldName2) {
46168
+ return humanizeFieldName(fieldName2).replace(/\sId$/, "").trim();
46131
46169
  }
46132
46170
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
46133
46171
  var init_List = __esm({
@@ -46195,9 +46233,9 @@ var init_List = __esm({
46195
46233
  };
46196
46234
  StatusBadge = ({
46197
46235
  value,
46198
- fieldName
46236
+ fieldName: fieldName2
46199
46237
  }) => {
46200
- const style = getStatusStyle(fieldName, value);
46238
+ const style = getStatusStyle(fieldName2, value);
46201
46239
  return /* @__PURE__ */ jsxs(
46202
46240
  Typography,
46203
46241
  {
@@ -49129,8 +49167,8 @@ var init_StatCard = __esm({
49129
49167
  return items.length;
49130
49168
  }
49131
49169
  if (field.includes(":")) {
49132
- const [fieldName, fieldValue] = field.split(":");
49133
- return items.filter((item) => item[fieldName] === fieldValue).length;
49170
+ const [fieldName2, fieldValue] = field.split(":");
49171
+ return items.filter((item) => item[fieldName2] === fieldValue).length;
49134
49172
  }
49135
49173
  const fieldExistsOnItems = items.some((item) => field in item);
49136
49174
  if (fieldExistsOnItems) {
@@ -50650,6 +50688,8 @@ var init_component_registry_generated = __esm({
50650
50688
  init_DocSearch();
50651
50689
  init_DocSidebar();
50652
50690
  init_DocTOC();
50691
+ init_DocumentDetails();
50692
+ init_DocumentPanel();
50653
50693
  init_DocumentViewer();
50654
50694
  init_DrawFxLayer();
50655
50695
  init_DrawGroup();
@@ -50759,7 +50799,7 @@ var init_component_registry_generated = __esm({
50759
50799
  init_RelationSelect();
50760
50800
  init_RepeatableFormSection();
50761
50801
  init_ReplyTree();
50762
- init_RichBlockEditor();
50802
+ init_RichTextEditor();
50763
50803
  init_RuntimeDebugger2();
50764
50804
  init_ScaledDiagram();
50765
50805
  init_ScoreDisplay();
@@ -50922,6 +50962,8 @@ var init_component_registry_generated = __esm({
50922
50962
  "DocSearch": DocSearch,
50923
50963
  "DocSidebar": DocSidebar,
50924
50964
  "DocTOC": DocTOC,
50965
+ "DocumentDetails": DocumentDetails,
50966
+ "DocumentPanel": DocumentPanel,
50925
50967
  "DocumentViewer": DocumentViewer,
50926
50968
  "DrawFxLayer": DrawFxLayer,
50927
50969
  "DrawGroup": DrawGroup,
@@ -51033,7 +51075,7 @@ var init_component_registry_generated = __esm({
51033
51075
  "RelationSelect": RelationSelect,
51034
51076
  "RepeatableFormSection": RepeatableFormSection,
51035
51077
  "ReplyTree": ReplyTree,
51036
- "RichBlockEditor": RichBlockEditor,
51078
+ "RichTextEditor": RichTextEditor,
51037
51079
  "RuntimeDebugger": RuntimeDebugger,
51038
51080
  "ScaledDiagram": ScaledDiagram,
51039
51081
  "ScoreDisplay": ScoreDisplay,
@@ -51187,10 +51229,10 @@ function enrichFormFields(fields, entityDef) {
51187
51229
  }
51188
51230
  if (field && typeof field === "object" && !Array.isArray(field) && !React94__default.isValidElement(field) && !(field instanceof Date)) {
51189
51231
  const obj = field;
51190
- const fieldName = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
51191
- if (!fieldName) return field;
51232
+ const fieldName2 = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
51233
+ if (!fieldName2) return field;
51192
51234
  if (obj.type || obj.inputType) return field;
51193
- const entityField = fieldMap.get(fieldName);
51235
+ const entityField = fieldMap.get(fieldName2);
51194
51236
  if (!entityField) return field;
51195
51237
  const enriched = { ...obj, type: entityField.type };
51196
51238
  if (entityField.required && !("required" in obj)) {
@@ -51240,9 +51282,9 @@ function enrichDetailFields(fields, entityDef) {
51240
51282
  }
51241
51283
  if (field && typeof field === "object" && !Array.isArray(field) && !React94__default.isValidElement(field) && !(field instanceof Date)) {
51242
51284
  const obj = field;
51243
- const fieldName = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
51244
- if (!fieldName || obj.type) return field;
51245
- const meta = metaFor(fieldName);
51285
+ const fieldName2 = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
51286
+ if (!fieldName2 || obj.type) return field;
51287
+ const meta = metaFor(fieldName2);
51246
51288
  return meta ? { ...obj, ...meta } : field;
51247
51289
  }
51248
51290
  return field;
@@ -52081,9 +52123,9 @@ function UISlotRenderer({
52081
52123
  "ui-slot-renderer relative min-h-full",
52082
52124
  className
52083
52125
  ), children: [
52084
- /* @__PURE__ */ jsxs(Box, { className: "flex min-h-full", children: [
52085
- /* @__PURE__ */ jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 shrink-0" }),
52086
- /* @__PURE__ */ jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-h-[200px]" })
52126
+ /* @__PURE__ */ jsxs(Box, { className: "flex min-h-full flex-col lg:flex-row", children: [
52127
+ /* @__PURE__ */ jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 lg:shrink-0" }),
52128
+ /* @__PURE__ */ jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-w-0 min-h-[200px]" })
52087
52129
  ] }),
52088
52130
  /* @__PURE__ */ jsx(UISlotComponent, { slot: "modal", portal: true }),
52089
52131
  /* @__PURE__ */ jsx(UISlotComponent, { slot: "drawer", portal: true }),
@@ -55980,7 +56022,7 @@ function buildTraitRenderConfig(binding, traitConfigsByName, payload) {
55980
56022
  }
55981
56023
  return void 0;
55982
56024
  }
55983
- function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
56025
+ function createSharedEntityWriter(binding, tick, traitStatesRef, emit, traitConfigsByName) {
55984
56026
  return (scratch) => {
55985
56027
  const traitName = binding.trait.name;
55986
56028
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -55989,9 +56031,13 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
55989
56031
  const writes = [];
55990
56032
  const ctx = createMinimalContext(scratchEntity, {}, currentState);
55991
56033
  const declaredDefaults = collectDeclaredConfigDefaults(binding.trait);
55992
- const callSiteConfig = getBindingConfig(binding);
55993
- if (declaredDefaults || callSiteConfig) {
55994
- ctx.config = { ...declaredDefaults ?? {}, ...callSiteConfig ?? {} };
56034
+ const resolvedByName = traitConfigsByName?.[traitName];
56035
+ const callSiteRaw = getBindingConfig(binding);
56036
+ const callSiteConfig = callSiteRaw ? Object.fromEntries(
56037
+ Object.entries(callSiteRaw).filter(([, v]) => !containsConfigForward(v))
56038
+ ) : void 0;
56039
+ if (declaredDefaults || resolvedByName || callSiteConfig) {
56040
+ ctx.config = { ...declaredDefaults ?? {}, ...resolvedByName ?? {}, ...callSiteConfig ?? {} };
55995
56041
  }
55996
56042
  ctx.mutateEntity = (changes) => {
55997
56043
  for (const [field, value] of Object.entries(changes)) {
@@ -56594,7 +56640,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56594
56640
  const interval = entries[0].tick.interval;
56595
56641
  const onDue = timedTick(`tick:shared:${group.storeKey}@${String(interval)}`, () => {
56596
56642
  const writers = entries.map(
56597
- ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
56643
+ ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter, traitConfigsByName)
56598
56644
  );
56599
56645
  runTickFrame(group.storeKey, writers, sharedEntityStore);
56600
56646
  });
@@ -59107,11 +59153,11 @@ function OrbInspector({ node, schema, editable = false, userType = "builder", th
59107
59153
  const handleAddField = useCallback(() => {
59108
59154
  eventBus.emit("UI:ADD_FIELD", {});
59109
59155
  }, [eventBus]);
59110
- const handleUpdateField = useCallback((fieldName, updates) => {
59111
- eventBus.emit("UI:UPDATE_FIELD", { fieldName, updates });
59156
+ const handleUpdateField = useCallback((fieldName2, updates) => {
59157
+ eventBus.emit("UI:UPDATE_FIELD", { fieldName: fieldName2, updates });
59112
59158
  }, [eventBus]);
59113
- const handleRemoveField = useCallback((fieldName) => {
59114
- eventBus.emit("UI:REMOVE_FIELD", { fieldName });
59159
+ const handleRemoveField = useCallback((fieldName2) => {
59160
+ eventBus.emit("UI:REMOVE_FIELD", { fieldName: fieldName2 });
59115
59161
  }, [eventBus]);
59116
59162
  const handleGuardChange = useCallback((guardExpr) => {
59117
59163
  if (!editable) return;