@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.
@@ -28612,8 +28612,9 @@ function DataGrid({
28612
28612
  const titleField = fieldDefs.find((f3) => f3.variant === "h3" || f3.variant === "h4") ?? fieldDefs[0];
28613
28613
  const badgeFields = fieldDefs.filter((f3) => f3.variant === "badge" && f3 !== titleField);
28614
28614
  const bodyFields = fieldDefs.filter((f3) => f3 !== titleField && !badgeFields.includes(f3));
28615
- const primaryActions = actionDefs.filter((a) => a.variant !== "danger");
28616
- const dangerActions = actionDefs.filter((a) => a.variant === "danger");
28615
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
28616
+ const inlineCardActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
28617
+ const menuCardActions = actionDefs.filter((a) => !inlineCardActions.includes(a));
28617
28618
  const fireAction = (action, itemData) => {
28618
28619
  if (action.navigatesTo) {
28619
28620
  const url = action.navigatesTo.replace(
@@ -28633,6 +28634,9 @@ function DataGrid({
28633
28634
  e.stopPropagation();
28634
28635
  fireAction(action, itemData);
28635
28636
  };
28637
+ const cardClickAction = actionDefs.find((a) => a.variant !== "danger");
28638
+ const handleCardClick = cardClickAction ? (itemData) => () => fireAction(cardClickAction, itemData) : void 0;
28639
+ const stopCardClick = handleCardClick ? (e) => e.stopPropagation() : void 0;
28636
28640
  const hasRenderProp = typeof children === "function";
28637
28641
  React94.useEffect(() => {
28638
28642
  if (data.length > 0 && !hasRenderProp && fieldDefs.length === 0) {
@@ -28704,55 +28708,40 @@ function DataGrid({
28704
28708
  {
28705
28709
  "data-entity-row": true,
28706
28710
  "data-entity-id": id,
28707
- className: cn("relative group/rowactions", isSelected && "ring-2 ring-primary rounded-lg"),
28711
+ onClick: handleCardClick?.(itemData),
28712
+ className: cn("relative group/rowactions", handleCardClick && "cursor-pointer", isSelected && "ring-2 ring-primary rounded-lg"),
28708
28713
  children: [
28709
28714
  children(itemData, index),
28710
- actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.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: [
28711
- /* @__PURE__ */ jsxRuntime.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: [
28712
- (maxInlineActions != null ? actionDefs.slice(0, maxInlineActions) : actionDefs).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
28713
- Button,
28714
- {
28715
- variant: action.variant === "primary" ? "primary" : "ghost",
28716
- size: "sm",
28717
- onClick: handleActionClick(action, itemData),
28718
- "data-testid": `action-${action.event}`,
28719
- "data-row-id": String(itemData.id),
28720
- className: action.variant === "danger" ? "text-error hover:text-error hover:bg-error/10" : void 0,
28721
- children: [
28722
- action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
28723
- action.label
28724
- ]
28725
- },
28726
- idx
28727
- )),
28728
- maxInlineActions != null && actionDefs.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
28729
- Menu,
28730
- {
28731
- position: "bottom-end",
28732
- trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
28733
- items: actionDefs.slice(maxInlineActions).map((action) => ({
28734
- label: action.label,
28735
- icon: action.icon,
28736
- event: action.event,
28737
- onClick: () => fireAction(action, itemData)
28738
- }))
28739
- }
28740
- )
28741
- ] }),
28742
- /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(
28715
+ actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: [
28716
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
28717
+ Button,
28718
+ {
28719
+ variant: "primary",
28720
+ size: "sm",
28721
+ onClick: handleActionClick(action, itemData),
28722
+ "data-testid": `action-${action.event}`,
28723
+ "data-row-id": String(itemData.id),
28724
+ children: [
28725
+ action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
28726
+ action.label
28727
+ ]
28728
+ },
28729
+ idx
28730
+ )),
28731
+ menuCardActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
28743
28732
  Menu,
28744
28733
  {
28745
28734
  position: "bottom-end",
28746
28735
  trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
28747
- items: actionDefs.map((action) => ({
28736
+ items: menuCardActions.map((action) => ({
28748
28737
  label: action.label,
28749
28738
  icon: action.icon,
28750
- event: action.event,
28739
+ variant: action.variant === "danger" ? "danger" : "default",
28751
28740
  onClick: () => fireAction(action, itemData)
28752
28741
  }))
28753
28742
  }
28754
- ) })
28755
- ] })
28743
+ )
28744
+ ] }) })
28756
28745
  ]
28757
28746
  },
28758
28747
  id
@@ -28766,12 +28755,14 @@ function DataGrid({
28766
28755
  {
28767
28756
  "data-entity-row": true,
28768
28757
  "data-entity-id": id,
28758
+ onClick: handleCardClick?.(itemData),
28769
28759
  className: cn(
28770
28760
  "bg-card rounded-lg",
28771
28761
  "border border-border",
28772
28762
  "shadow-elevation-card hover:shadow-elevation-dialog",
28773
28763
  "hover:border-primary transition-all",
28774
28764
  "flex flex-col",
28765
+ handleCardClick && "cursor-pointer",
28775
28766
  isSelected && "ring-2 ring-primary border-primary"
28776
28767
  ),
28777
28768
  children: [
@@ -28821,53 +28812,35 @@ function DataGrid({
28821
28812
  ] }, field.name);
28822
28813
  }) })
28823
28814
  ] }),
28824
- (primaryActions.length > 0 || dangerActions.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
28825
- (maxInlineActions != null ? primaryActions.slice(0, maxInlineActions) : primaryActions).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
28815
+ actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", onClick: stopCardClick, className: "flex-shrink-0", children: [
28816
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
28826
28817
  Button,
28827
28818
  {
28828
- variant: action.variant === "primary" ? "primary" : "ghost",
28819
+ variant: "primary",
28829
28820
  size: "sm",
28830
28821
  onClick: handleActionClick(action, itemData),
28831
28822
  "data-testid": `action-${action.event}`,
28832
28823
  "data-row-id": String(itemData.id),
28833
28824
  "aria-label": action.label,
28834
28825
  title: action.label,
28835
- className: cn(
28836
- action.variant === "primary" ? void 0 : "text-muted-foreground hover:text-foreground",
28837
- action.icon && "px-2"
28838
- ),
28826
+ className: cn(action.icon && "px-2"),
28839
28827
  children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
28840
28828
  },
28841
28829
  idx
28842
28830
  )),
28843
- maxInlineActions != null && primaryActions.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
28831
+ menuCardActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
28844
28832
  Menu,
28845
28833
  {
28846
28834
  position: "bottom-end",
28847
28835
  trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
28848
- items: primaryActions.slice(maxInlineActions).map((action) => ({
28836
+ items: menuCardActions.map((action) => ({
28849
28837
  label: action.label,
28850
28838
  icon: action.icon,
28851
- event: action.event,
28839
+ variant: action.variant === "danger" ? "danger" : "default",
28852
28840
  onClick: () => fireAction(action, itemData)
28853
28841
  }))
28854
28842
  }
28855
- ),
28856
- dangerActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
28857
- Button,
28858
- {
28859
- variant: "ghost",
28860
- size: "sm",
28861
- onClick: handleActionClick(action, itemData),
28862
- "data-testid": `action-${action.event}`,
28863
- "data-row-id": String(itemData.id),
28864
- "aria-label": action.label,
28865
- title: action.label,
28866
- className: "text-error hover:text-error hover:bg-error/10 px-2",
28867
- children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
28868
- },
28869
- `danger-${idx}`
28870
- ))
28843
+ )
28871
28844
  ] })
28872
28845
  ] }) }),
28873
28846
  bodyFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-4 pt-2 pb-4 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
@@ -28888,8 +28861,8 @@ function DataGrid({
28888
28861
  }
28889
28862
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
28890
28863
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
28891
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
28892
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", children: formatValue(value, field.format) })
28864
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
28865
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
28893
28866
  ] }, field.name);
28894
28867
  }) })
28895
28868
  ] }) })
@@ -29109,54 +29082,59 @@ function DataList({
29109
29082
  if (!itemActions || itemActions.length === 0) return null;
29110
29083
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
29111
29084
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
29112
- return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
29113
- inline.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
29114
- Button,
29115
- {
29116
- variant: action.variant ?? "ghost",
29117
- size: "sm",
29118
- onClick: handleActionClick(action, itemData),
29119
- "data-testid": `action-${action.event}`,
29120
- "data-row-id": String(itemData.id),
29121
- className: cn(
29122
- action.variant === "danger" && "text-error hover:bg-error/10",
29123
- // Must sit on the Button itself: the variant's own text colour
29124
- // beats an inherited one from the row wrapper.
29125
- onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
29126
- ),
29127
- children: [
29128
- action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
29129
- action.label
29130
- ]
29131
- },
29132
- idx
29133
- )),
29134
- overflow.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
29135
- Menu,
29136
- {
29137
- position: "bottom-end",
29138
- trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
29139
- items: overflow.map((action) => ({
29140
- label: action.label,
29141
- icon: action.icon,
29142
- event: action.event,
29143
- variant: action.variant === "danger" ? "danger" : "default",
29144
- onClick: () => eventBus.emit(`UI:${action.event}`, {
29145
- id: itemData.id,
29146
- row: itemData
29147
- })
29148
- }))
29149
- }
29150
- )
29151
- ] });
29085
+ return (
29086
+ // stopPropagation at the cluster: the "⋯" trigger opens the overflow
29087
+ // menu without also firing the row's default click (inline buttons
29088
+ // already stop it in handleActionClick; the Menu panel is portaled).
29089
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, className: "flex-shrink-0", children: [
29090
+ inline.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
29091
+ Button,
29092
+ {
29093
+ variant: action.variant ?? "ghost",
29094
+ size: "sm",
29095
+ onClick: handleActionClick(action, itemData),
29096
+ "data-testid": `action-${action.event}`,
29097
+ "data-row-id": String(itemData.id),
29098
+ className: cn(
29099
+ action.variant === "danger" && "text-error hover:bg-error/10",
29100
+ // Must sit on the Button itself: the variant's own text colour
29101
+ // beats an inherited one from the row wrapper.
29102
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
29103
+ ),
29104
+ children: [
29105
+ action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
29106
+ action.label
29107
+ ]
29108
+ },
29109
+ idx
29110
+ )),
29111
+ overflow.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
29112
+ Menu,
29113
+ {
29114
+ position: "bottom-end",
29115
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
29116
+ items: overflow.map((action) => ({
29117
+ label: action.label,
29118
+ icon: action.icon,
29119
+ variant: action.variant === "danger" ? "danger" : "default",
29120
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
29121
+ id: itemData.id,
29122
+ row: itemData
29123
+ })
29124
+ }))
29125
+ }
29126
+ )
29127
+ ] })
29128
+ );
29152
29129
  };
29130
+ const rowClickEvent = itemClickEvent || itemActions?.find((a) => a.variant !== "danger")?.event;
29153
29131
  const handleRowClick = (itemData) => () => {
29154
- if (!itemClickEvent) return;
29132
+ if (!rowClickEvent) return;
29155
29133
  const payload = {
29156
29134
  id: itemData.id,
29157
29135
  row: itemData
29158
29136
  };
29159
- eventBus.emit(`UI:${itemClickEvent}`, payload);
29137
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
29160
29138
  };
29161
29139
  if (isLoading) {
29162
29140
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
@@ -29204,10 +29182,10 @@ function DataList({
29204
29182
  {
29205
29183
  "data-entity-row": true,
29206
29184
  "data-entity-id": id,
29207
- onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
29185
+ onClick: rowClickEvent ? handleRowClick(itemData) : void 0,
29208
29186
  className: cn(
29209
29187
  "flex px-4 group/rowactions",
29210
- itemClickEvent && "cursor-pointer",
29188
+ rowClickEvent && "cursor-pointer",
29211
29189
  isSent ? "justify-end" : "justify-start"
29212
29190
  ),
29213
29191
  children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -29283,7 +29261,7 @@ function DataList({
29283
29261
  const id2 = itemData.id || String(index);
29284
29262
  const actions = renderItemActions(itemData);
29285
29263
  return wrapDnd(
29286
- /* @__PURE__ */ jsxRuntime.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: [
29264
+ /* @__PURE__ */ jsxRuntime.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: [
29287
29265
  children(itemData, index),
29288
29266
  actions && /* @__PURE__ */ jsxRuntime.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: [
29289
29267
  /* @__PURE__ */ jsxRuntime.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 }),
@@ -29295,7 +29273,6 @@ function DataList({
29295
29273
  items: (itemActions ?? []).map((action) => ({
29296
29274
  label: action.label,
29297
29275
  icon: action.icon,
29298
- event: action.event,
29299
29276
  variant: action.variant === "danger" ? "danger" : "default",
29300
29277
  onClick: () => eventBus.emit(`UI:${action.event}`, {
29301
29278
  id: itemData.id,
@@ -29312,12 +29289,16 @@ function DataList({
29312
29289
  const id = itemData.id || String(index);
29313
29290
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
29314
29291
  return wrapDnd(
29315
- /* @__PURE__ */ jsxRuntime.jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn(itemClickEvent && "cursor-pointer"), children: [
29292
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
29316
29293
  /* @__PURE__ */ jsxRuntime.jsxs(
29317
29294
  Box,
29318
29295
  {
29319
29296
  className: cn(
29320
- "group flex items-center gap-4 transition-all duration-fast",
29297
+ // items-start, not items-center: a multi-line row (title + meta +
29298
+ // progress) centred its action cluster in the vertical middle, so
29299
+ // the buttons floated between the meta fields instead of anchoring
29300
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
29301
+ "group flex items-start gap-4 transition-all duration-fast",
29321
29302
  isCompact ? "px-4 py-2" : "px-6 py-4",
29322
29303
  "hover:bg-muted/80",
29323
29304
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -29348,11 +29329,19 @@ function DataList({
29348
29329
  if (value === void 0 || value === null || value === "") return null;
29349
29330
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
29350
29331
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
29351
- /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", color: "secondary", children: [
29352
- field.label ?? fieldLabel3(field.name),
29353
- ":"
29354
- ] }),
29355
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
29332
+ /* @__PURE__ */ jsxRuntime.jsxs(
29333
+ Typography,
29334
+ {
29335
+ variant: "caption",
29336
+ color: "secondary",
29337
+ className: cn(field.format !== "boolean" && "sr-only"),
29338
+ children: [
29339
+ field.label ?? fieldLabel3(field.name),
29340
+ ":"
29341
+ ]
29342
+ }
29343
+ ),
29344
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
29356
29345
  ] }, field.name);
29357
29346
  }) }),
29358
29347
  progressFields.map((field) => {
@@ -29371,7 +29360,7 @@ function DataList({
29371
29360
  ]
29372
29361
  }
29373
29362
  ),
29374
- isCard && !isLast && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-6 border-b border-border/40" })
29363
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
29375
29364
  ] }, id)
29376
29365
  );
29377
29366
  };
@@ -29381,7 +29370,13 @@ function DataList({
29381
29370
  {
29382
29371
  className: cn(
29383
29372
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
29384
- !isCard && gapClass,
29373
+ // `gap-*` is inert on a block container, and Box only emits a display
29374
+ // class when its `display` prop is set — so every non-card list had
29375
+ // been asking for a gap that CSS silently dropped. flex-col makes it
29376
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
29377
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
29378
+ !isCard && "flex flex-col",
29379
+ !isCard && !isCompact && gapClass,
29385
29380
  listLookStyles[look],
29386
29381
  className
29387
29382
  ),
@@ -34570,7 +34565,7 @@ var init_MapView = __esm({
34570
34565
  shadowSize: [41, 41]
34571
34566
  });
34572
34567
  L.Marker.prototype.options.icon = defaultIcon;
34573
- const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback96, useState: useState100 } = React94__namespace.default;
34568
+ const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback96, useState: useState102 } = React94__namespace.default;
34574
34569
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
34575
34570
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
34576
34571
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -34615,7 +34610,7 @@ var init_MapView = __esm({
34615
34610
  showAttribution = true
34616
34611
  }) {
34617
34612
  const eventBus = useEventBus2();
34618
- const [clickedPosition, setClickedPosition] = useState100(null);
34613
+ const [clickedPosition, setClickedPosition] = useState102(null);
34619
34614
  const handleMapClick = useCallback96((lat, lng) => {
34620
34615
  if (showClickedPin) {
34621
34616
  setClickedPosition({ lat, lng });
@@ -38977,741 +38972,775 @@ var init_PositionedCanvas = __esm({
38977
38972
  PositionedCanvas.displayName = "PositionedCanvas";
38978
38973
  }
38979
38974
  });
38980
- function nextBlockId(prefix = "blk") {
38981
- _idSeq += 1;
38982
- const random = Math.random().toString(36).slice(2, 8);
38983
- return `${prefix}-${Date.now().toString(36)}-${_idSeq}-${random}`;
38975
+ function safeUrl(raw, kinds) {
38976
+ const url = raw.trim();
38977
+ if (url.startsWith("#") || url.startsWith("/")) return url;
38978
+ const lower = url.toLowerCase();
38979
+ for (const kind of kinds) {
38980
+ if (lower.startsWith(kind)) return url;
38981
+ }
38982
+ return null;
38984
38983
  }
38985
- function normalizeBlocks(raw) {
38986
- if (!Array.isArray(raw) || raw.length === 0) return [createBlock("paragraph")];
38987
- return raw.map((row) => {
38988
- const entity = row;
38989
- const rawType = entity.type;
38990
- const type = typeof rawType === "string" && BLOCK_TYPES.has(rawType) ? rawType : "paragraph";
38991
- const id = typeof entity.id === "string" && entity.id ? entity.id : nextBlockId(type);
38992
- return { ...entity, id, type };
38993
- });
38984
+ function sanitizeNode(el) {
38985
+ const children = Array.from(el.children);
38986
+ for (const child of children) {
38987
+ const tag = child.tagName.toLowerCase();
38988
+ if (DROP_TAGS.has(tag)) {
38989
+ child.remove();
38990
+ continue;
38991
+ }
38992
+ const allowed = ALLOWED_ATTRS[tag];
38993
+ if (!allowed) {
38994
+ sanitizeNode(child);
38995
+ child.replaceWith(...Array.from(child.childNodes));
38996
+ continue;
38997
+ }
38998
+ for (const attr of Array.from(child.attributes)) {
38999
+ const name = attr.name.toLowerCase();
39000
+ if (!allowed.includes(name)) {
39001
+ child.removeAttribute(attr.name);
39002
+ continue;
39003
+ }
39004
+ if (name === "href") {
39005
+ const url = safeUrl(attr.value, ["http://", "https://", "mailto:"]);
39006
+ if (url === null) child.removeAttribute(attr.name);
39007
+ else child.setAttribute("href", url);
39008
+ }
39009
+ if (name === "src") {
39010
+ const url = safeUrl(attr.value, ["http://", "https://", "data:image/"]);
39011
+ if (url === null) child.remove();
39012
+ else child.setAttribute("src", url);
39013
+ }
39014
+ }
39015
+ sanitizeNode(child);
39016
+ }
38994
39017
  }
38995
- function createBlock(type) {
38996
- switch (type) {
38997
- case "bullet-list":
38998
- case "numbered-list":
38999
- return {
39000
- id: nextBlockId(type),
39001
- type,
39002
- children: [
39003
- { id: nextBlockId("li"), type: "paragraph", content: "" }
39004
- ]
39005
- };
39006
- case "image":
39007
- return {
39008
- id: nextBlockId(type),
39009
- type,
39010
- content: "",
39011
- metadata: { url: "", caption: "" }
39012
- };
39013
- case "code":
39014
- return {
39015
- id: nextBlockId(type),
39016
- type,
39017
- content: "",
39018
- metadata: { language: "plaintext" }
39019
- };
39020
- case "divider":
39021
- return { id: nextBlockId(type), type };
39022
- default:
39023
- return { id: nextBlockId(type), type, content: "" };
39018
+ function sanitizeRichHtml(html) {
39019
+ if (!html || typeof html !== "string") return "";
39020
+ if (typeof window === "undefined" || typeof DOMParser === "undefined") return "";
39021
+ const doc = new DOMParser().parseFromString(html, "text/html");
39022
+ sanitizeNode(doc.body);
39023
+ if (!doc.body.querySelector("img, hr") && (doc.body.textContent ?? "").trim().length === 0) {
39024
+ return "";
39024
39025
  }
39026
+ return doc.body.innerHTML.replace(/​/g, "");
39025
39027
  }
39026
- function replaceBlock(blocks, id, updater) {
39027
- return blocks.map((block) => block.id === id ? updater(block) : block);
39028
+ function htmlIsEmpty(el) {
39029
+ if (el.querySelector("img, hr")) return false;
39030
+ return (el.textContent ?? "").trim().length === 0;
39028
39031
  }
39029
- function removeBlock(blocks, id) {
39030
- return blocks.filter((block) => block.id !== id);
39032
+ function closestBlock(node, root) {
39033
+ let cur = node;
39034
+ while (cur && cur !== root) {
39035
+ if (cur instanceof Element && BLOCK_TAGS.has(cur.tagName)) return cur;
39036
+ cur = cur.parentNode;
39037
+ }
39038
+ return null;
39031
39039
  }
39032
- function duplicateBlock(block) {
39040
+ function RichTextStyles() {
39041
+ return /* @__PURE__ */ jsxRuntime.jsx("style", { children: RICH_TEXT_CSS });
39042
+ }
39043
+ function readToolbarState(root) {
39044
+ const sel = window.getSelection();
39045
+ if (!sel || sel.rangeCount === 0) return IDLE_TOOLBAR;
39046
+ const anchor = sel.anchorNode;
39047
+ if (!anchor || !root.contains(anchor)) return IDLE_TOOLBAR;
39048
+ let block = null;
39049
+ let link = false;
39050
+ let node = anchor;
39051
+ while (node && node !== root) {
39052
+ if (node instanceof Element) {
39053
+ const tag = node.tagName.toLowerCase();
39054
+ if (!block && (tag === "h1" || tag === "h2" || tag === "h3" || tag === "blockquote" || tag === "pre")) {
39055
+ block = tag;
39056
+ }
39057
+ if (tag === "a") link = true;
39058
+ }
39059
+ node = node.parentNode;
39060
+ }
39033
39061
  return {
39034
- ...block,
39035
- id: nextBlockId(block.type),
39036
- children: block.children?.map((child) => ({
39037
- ...child,
39038
- id: nextBlockId("li")
39039
- })),
39040
- metadata: block.metadata ? { ...block.metadata } : void 0
39062
+ bold: document.queryCommandState("bold"),
39063
+ italic: document.queryCommandState("italic"),
39064
+ underline: document.queryCommandState("underline"),
39065
+ strikeThrough: document.queryCommandState("strikeThrough"),
39066
+ block,
39067
+ bullets: document.queryCommandState("insertUnorderedList"),
39068
+ numbers: document.queryCommandState("insertOrderedList"),
39069
+ link
39041
39070
  };
39042
39071
  }
39043
- function insertAfter(blocks, targetId, inserted) {
39044
- const idx = blocks.findIndex((b) => b.id === targetId);
39045
- if (idx === -1) return [...blocks, inserted];
39046
- const next = blocks.slice();
39047
- next.splice(idx + 1, 0, inserted);
39048
- return next;
39049
- }
39050
- function changeBlockType(block, type) {
39051
- if (block.type === type) return block;
39052
- if (type === "bullet-list" || type === "numbered-list") {
39053
- if (block.children && block.children.length > 0) {
39054
- return { ...block, type };
39072
+ function ToolbarButton({ icon: IconCmp, label, active, onExec }) {
39073
+ return /* @__PURE__ */ jsxRuntime.jsx(
39074
+ Button,
39075
+ {
39076
+ type: "button",
39077
+ variant: "ghost",
39078
+ size: "sm",
39079
+ "aria-label": label,
39080
+ "aria-pressed": active,
39081
+ title: label,
39082
+ className: cn(
39083
+ "h-8 w-8 p-0 gap-0 justify-center",
39084
+ active && "bg-muted text-foreground"
39085
+ ),
39086
+ onMouseDown: (e) => {
39087
+ e.preventDefault();
39088
+ onExec();
39089
+ },
39090
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconCmp, { size: 15 })
39055
39091
  }
39056
- const seed2 = block.content ?? "";
39057
- return {
39058
- id: block.id,
39059
- type,
39060
- children: [
39061
- { id: nextBlockId("li"), type: "paragraph", content: seed2 }
39062
- ]
39063
- };
39064
- }
39065
- if (type === "divider") {
39066
- return { id: block.id, type };
39067
- }
39068
- if (type === "image") {
39069
- return {
39070
- id: block.id,
39071
- type,
39072
- content: "",
39073
- metadata: { url: "", caption: block.content ?? "" }
39074
- };
39075
- }
39076
- if (type === "code") {
39077
- return {
39078
- id: block.id,
39079
- type,
39080
- content: block.content ?? "",
39081
- metadata: { language: "plaintext" }
39092
+ );
39093
+ }
39094
+ var CHANGE_DEBOUNCE_MS, ALLOWED_ATTRS, DROP_TAGS, MD_BLOCK_PREFIXES, BLOCK_TAGS, MD_INLINE_RULES, RICH_TEXT_CSS, IDLE_TOOLBAR, RichTextEditor;
39095
+ var init_RichTextEditor = __esm({
39096
+ "components/core/molecules/RichTextEditor.tsx"() {
39097
+ "use client";
39098
+ init_cn();
39099
+ init_Box();
39100
+ init_Button();
39101
+ init_useEventBus();
39102
+ CHANGE_DEBOUNCE_MS = 400;
39103
+ ALLOWED_ATTRS = {
39104
+ p: [],
39105
+ h1: [],
39106
+ h2: [],
39107
+ h3: [],
39108
+ h4: [],
39109
+ ul: [],
39110
+ ol: [],
39111
+ li: [],
39112
+ blockquote: [],
39113
+ pre: [],
39114
+ code: [],
39115
+ b: [],
39116
+ strong: [],
39117
+ i: [],
39118
+ em: [],
39119
+ u: [],
39120
+ s: [],
39121
+ strike: [],
39122
+ br: [],
39123
+ hr: [],
39124
+ div: [],
39125
+ span: [],
39126
+ a: ["href"],
39127
+ img: ["src", "alt"]
39128
+ };
39129
+ DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "link", "meta", "form", "input", "button", "textarea", "select"]);
39130
+ MD_BLOCK_PREFIXES = {
39131
+ "#": { kind: "block", tag: "h1" },
39132
+ "##": { kind: "block", tag: "h2" },
39133
+ "###": { kind: "block", tag: "h3" },
39134
+ ">": { kind: "block", tag: "blockquote" },
39135
+ "```": { kind: "block", tag: "pre" },
39136
+ "-": { kind: "list", command: "insertUnorderedList" },
39137
+ "*": { kind: "list", command: "insertUnorderedList" },
39138
+ "1.": { kind: "list", command: "insertOrderedList" }
39139
+ };
39140
+ BLOCK_TAGS = /* @__PURE__ */ new Set(["P", "DIV", "LI", "H1", "H2", "H3", "H4", "BLOCKQUOTE", "PRE"]);
39141
+ MD_INLINE_RULES = [
39142
+ { pattern: /\*\*([^*\n]+)\*\*$/, wrap: "strong" },
39143
+ { pattern: /(^|[^*])\*([^*\n]+)\*$/, wrap: "em" },
39144
+ { pattern: /`([^`\n]+)`$/, wrap: "code" }
39145
+ ];
39146
+ RICH_TEXT_CSS = `
39147
+ .almadar-rich-text { line-height: 1.7; color: var(--color-foreground); }
39148
+ .almadar-rich-text h1 { font-size: 1.875rem; font-weight: 700; line-height: 1.25; margin: 1.25em 0 0.4em; }
39149
+ .almadar-rich-text h2 { font-size: 1.5rem; font-weight: 650; line-height: 1.3; margin: 1.1em 0 0.4em; }
39150
+ .almadar-rich-text h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.35; margin: 1em 0 0.35em; }
39151
+ .almadar-rich-text h1:first-child, .almadar-rich-text h2:first-child, .almadar-rich-text h3:first-child { margin-top: 0; }
39152
+ .almadar-rich-text p { margin: 0.5em 0; }
39153
+ .almadar-rich-text ul { list-style: disc; padding-inline-start: 1.5rem; margin: 0.5em 0; }
39154
+ .almadar-rich-text ol { list-style: decimal; padding-inline-start: 1.5rem; margin: 0.5em 0; }
39155
+ .almadar-rich-text li { margin: 0.25em 0; }
39156
+ .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; }
39157
+ .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; }
39158
+ .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; }
39159
+ .almadar-rich-text pre code { background: transparent; padding: 0; }
39160
+ .almadar-rich-text a { color: var(--color-primary); text-decoration: underline; }
39161
+ .almadar-rich-text hr { border: none; border-top: 1px solid var(--color-border); margin: 1.25em 0; }
39162
+ .almadar-rich-text img { max-width: 100%; border-radius: 0.375rem; }
39163
+ `;
39164
+ IDLE_TOOLBAR = {
39165
+ bold: false,
39166
+ italic: false,
39167
+ underline: false,
39168
+ strikeThrough: false,
39169
+ block: null,
39170
+ bullets: false,
39171
+ numbers: false,
39172
+ link: false
39173
+ };
39174
+ RichTextEditor = ({
39175
+ value,
39176
+ onChange,
39177
+ changeEvent,
39178
+ readOnly = false,
39179
+ placeholder,
39180
+ showToolbar = true,
39181
+ className
39182
+ }) => {
39183
+ const { t } = hooks.useTranslate();
39184
+ const ref = React94.useRef(null);
39185
+ const [toolbar, setToolbar] = React94.useState(IDLE_TOOLBAR);
39186
+ const [empty, setEmpty] = React94.useState(() => !value || !sanitizeRichHtml(value).trim());
39187
+ const eventBus = useEventBus();
39188
+ const onChangeRef = React94.useRef(onChange);
39189
+ const changeEventRef = React94.useRef(changeEvent);
39190
+ React94.useEffect(() => {
39191
+ onChangeRef.current = onChange;
39192
+ changeEventRef.current = changeEvent;
39193
+ }, [onChange, changeEvent]);
39194
+ const emitTimerRef = React94.useRef(null);
39195
+ const emitNow = React94.useCallback(() => {
39196
+ const el = ref.current;
39197
+ if (!el) return;
39198
+ const html = sanitizeRichHtml(el.innerHTML);
39199
+ onChangeRef.current?.(html);
39200
+ const evt = changeEventRef.current;
39201
+ if (evt) eventBus.emit(`UI:${evt}`, { value: html });
39202
+ }, [eventBus]);
39203
+ const scheduleEmit = React94.useCallback(() => {
39204
+ if (emitTimerRef.current) clearTimeout(emitTimerRef.current);
39205
+ emitTimerRef.current = setTimeout(() => {
39206
+ emitTimerRef.current = null;
39207
+ emitNow();
39208
+ }, CHANGE_DEBOUNCE_MS);
39209
+ }, [emitNow]);
39210
+ const flushEmit = React94.useCallback(() => {
39211
+ if (!emitTimerRef.current) return;
39212
+ clearTimeout(emitTimerRef.current);
39213
+ emitTimerRef.current = null;
39214
+ emitNow();
39215
+ }, [emitNow]);
39216
+ React94.useEffect(() => () => {
39217
+ if (emitTimerRef.current) {
39218
+ clearTimeout(emitTimerRef.current);
39219
+ emitTimerRef.current = null;
39220
+ emitNow();
39221
+ }
39222
+ }, [emitNow]);
39223
+ React94.useEffect(() => {
39224
+ if (readOnly) return;
39225
+ const el = ref.current;
39226
+ if (!el) return;
39227
+ if (document.activeElement === el) return;
39228
+ const next = sanitizeRichHtml(value ?? "") || "<p><br></p>";
39229
+ if (el.innerHTML !== next) {
39230
+ el.innerHTML = next;
39231
+ setEmpty(htmlIsEmpty(el));
39232
+ }
39233
+ }, [readOnly, value]);
39234
+ React94.useEffect(() => {
39235
+ if (readOnly) return;
39236
+ document.execCommand("defaultParagraphSeparator", false, "p");
39237
+ }, [readOnly]);
39238
+ React94.useEffect(() => {
39239
+ if (readOnly || !showToolbar) return;
39240
+ const onSelectionChange = () => {
39241
+ const el = ref.current;
39242
+ if (!el) return;
39243
+ setToolbar(readToolbarState(el));
39244
+ };
39245
+ document.addEventListener("selectionchange", onSelectionChange);
39246
+ return () => document.removeEventListener("selectionchange", onSelectionChange);
39247
+ }, [readOnly, showToolbar]);
39248
+ const tryInlineMarkdown = React94.useCallback(() => {
39249
+ const root = ref.current;
39250
+ if (!root) return false;
39251
+ const sel = window.getSelection();
39252
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
39253
+ const node = sel.anchorNode;
39254
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
39255
+ if (node.parentElement?.closest("code, pre")) return false;
39256
+ const text = (node.textContent ?? "").slice(0, sel.anchorOffset);
39257
+ for (const rule of MD_INLINE_RULES) {
39258
+ const m = rule.pattern.exec(text);
39259
+ if (!m) continue;
39260
+ const inner = rule.wrap === "em" ? m[2] : m[1];
39261
+ if (!inner || !inner.trim()) continue;
39262
+ const lead = rule.wrap === "em" ? m[1]?.length ?? 0 : 0;
39263
+ const range = document.createRange();
39264
+ range.setStart(node, m.index + lead);
39265
+ range.setEnd(node, sel.anchorOffset);
39266
+ const el = document.createElement(rule.wrap);
39267
+ el.textContent = inner;
39268
+ range.deleteContents();
39269
+ range.insertNode(el);
39270
+ const after = document.createTextNode("\u200B");
39271
+ el.parentNode?.insertBefore(after, el.nextSibling);
39272
+ const caret = document.createRange();
39273
+ caret.setStart(after, 1);
39274
+ caret.collapse(true);
39275
+ sel.removeAllRanges();
39276
+ sel.addRange(caret);
39277
+ return true;
39278
+ }
39279
+ return false;
39280
+ }, []);
39281
+ const tryBlockMarkdown = React94.useCallback(() => {
39282
+ const root = ref.current;
39283
+ if (!root) return false;
39284
+ const sel = window.getSelection();
39285
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
39286
+ const node = sel.anchorNode;
39287
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
39288
+ const block = closestBlock(node, root);
39289
+ if (block ? block.firstChild !== node : root.firstChild !== node) return false;
39290
+ const prefix = (node.textContent ?? "").slice(0, sel.anchorOffset);
39291
+ const rule = MD_BLOCK_PREFIXES[prefix];
39292
+ if (!rule) return false;
39293
+ if (node.parentElement?.closest("pre, code")) return false;
39294
+ if (rule.kind === "list" && node.parentElement?.closest("li")) return false;
39295
+ node.deleteData(0, prefix.length);
39296
+ const caret = document.createRange();
39297
+ caret.setStart(node, 0);
39298
+ caret.collapse(true);
39299
+ sel.removeAllRanges();
39300
+ sel.addRange(caret);
39301
+ if (rule.kind === "list") {
39302
+ const container = block ?? node.parentElement;
39303
+ const list = document.createElement(rule.command === "insertOrderedList" ? "ol" : "ul");
39304
+ const li = document.createElement("li");
39305
+ if (block) {
39306
+ while (block.firstChild) li.appendChild(block.firstChild);
39307
+ list.appendChild(li);
39308
+ block.replaceWith(list);
39309
+ } else if (container) {
39310
+ li.appendChild(node);
39311
+ list.appendChild(li);
39312
+ container.appendChild(list);
39313
+ }
39314
+ if (!li.firstChild) li.appendChild(document.createElement("br"));
39315
+ const liCaret = document.createRange();
39316
+ liCaret.selectNodeContents(li);
39317
+ liCaret.collapse(true);
39318
+ sel.removeAllRanges();
39319
+ sel.addRange(liCaret);
39320
+ } else {
39321
+ document.execCommand("formatBlock", false, rule.tag);
39322
+ }
39323
+ return true;
39324
+ }, []);
39325
+ const afterEdit = React94.useCallback(() => {
39326
+ const el = ref.current;
39327
+ if (!el) return;
39328
+ tryInlineMarkdown();
39329
+ setEmpty(htmlIsEmpty(el));
39330
+ setToolbar(readToolbarState(el));
39331
+ scheduleEmit();
39332
+ }, [scheduleEmit, tryInlineMarkdown]);
39333
+ const handleKeyDown = React94.useCallback((e) => {
39334
+ if (e.key === " " && tryBlockMarkdown()) {
39335
+ e.preventDefault();
39336
+ afterEdit();
39337
+ }
39338
+ }, [afterEdit, tryBlockMarkdown]);
39339
+ const execInline = React94.useCallback((command) => {
39340
+ document.execCommand(command, false);
39341
+ afterEdit();
39342
+ }, [afterEdit]);
39343
+ const execBlock = React94.useCallback((tag) => {
39344
+ const next = toolbar.block === tag ? "p" : tag;
39345
+ document.execCommand("formatBlock", false, next);
39346
+ afterEdit();
39347
+ }, [afterEdit, toolbar.block]);
39348
+ const execList = React94.useCallback((command) => {
39349
+ document.execCommand(command, false);
39350
+ afterEdit();
39351
+ }, [afterEdit]);
39352
+ const execLink = React94.useCallback(() => {
39353
+ if (toolbar.link) {
39354
+ document.execCommand("unlink", false);
39355
+ afterEdit();
39356
+ return;
39357
+ }
39358
+ const url = window.prompt(t("richTextEditor.linkPrompt"));
39359
+ if (!url) return;
39360
+ const safe = safeUrl(url, ["http://", "https://", "mailto:"]) ?? `https://${url}`;
39361
+ document.execCommand("createLink", false, safe);
39362
+ afterEdit();
39363
+ }, [afterEdit, t, toolbar.link]);
39364
+ const execRule = React94.useCallback(() => {
39365
+ document.execCommand("insertHorizontalRule", false);
39366
+ afterEdit();
39367
+ }, [afterEdit]);
39368
+ if (readOnly) {
39369
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("almadar-rich-text max-w-none", className), children: [
39370
+ /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
39371
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { dangerouslySetInnerHTML: { __html: sanitizeRichHtml(value ?? "") } })
39372
+ ] });
39373
+ }
39374
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
39375
+ /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
39376
+ showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
39377
+ Box,
39378
+ {
39379
+ role: "toolbar",
39380
+ "aria-label": t("richTextEditor.editorToolbar"),
39381
+ className: cn(
39382
+ "sticky top-0 z-10 flex flex-wrap items-center gap-0.5 self-start",
39383
+ "rounded-md border border-border bg-background/95 px-1 py-0.5 shadow-sm"
39384
+ ),
39385
+ children: [
39386
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Bold, label: t("richTextEditor.bold"), active: toolbar.bold, onExec: () => execInline("bold") }),
39387
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Italic, label: t("richTextEditor.italic"), active: toolbar.italic, onExec: () => execInline("italic") }),
39388
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Underline, label: t("richTextEditor.underline"), active: toolbar.underline, onExec: () => execInline("underline") }),
39389
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Strikethrough, label: t("richTextEditor.strikethrough"), active: toolbar.strikeThrough, onExec: () => execInline("strikeThrough") }),
39390
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
39391
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading1, label: t("richTextEditor.heading1"), active: toolbar.block === "h1", onExec: () => execBlock("h1") }),
39392
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading2, label: t("richTextEditor.heading2"), active: toolbar.block === "h2", onExec: () => execBlock("h2") }),
39393
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading3, label: t("richTextEditor.heading3"), active: toolbar.block === "h3", onExec: () => execBlock("h3") }),
39394
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
39395
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.List, label: t("richTextEditor.bulletList"), active: toolbar.bullets, onExec: () => execList("insertUnorderedList") }),
39396
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.ListOrdered, label: t("richTextEditor.numberedList"), active: toolbar.numbers, onExec: () => execList("insertOrderedList") }),
39397
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
39398
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
39399
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
39400
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Link, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
39401
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Minus, label: t("richTextEditor.divider"), onExec: execRule })
39402
+ ]
39403
+ }
39404
+ ),
39405
+ /* @__PURE__ */ jsxRuntime.jsx(
39406
+ Box,
39407
+ {
39408
+ ref,
39409
+ contentEditable: true,
39410
+ suppressContentEditableWarning: true,
39411
+ role: "textbox",
39412
+ "aria-multiline": "true",
39413
+ "aria-label": t("richTextEditor.editorSurface"),
39414
+ "data-placeholder": placeholder ?? t("richTextEditor.placeholder"),
39415
+ "data-empty": empty ? "true" : "false",
39416
+ className: cn(
39417
+ "almadar-rich-text max-w-none min-h-[8rem] outline-none",
39418
+ "data-[empty=true]:before:content-[attr(data-placeholder)]",
39419
+ "data-[empty=true]:before:text-muted-foreground/60",
39420
+ "data-[empty=true]:before:pointer-events-none",
39421
+ "data-[empty=true]:before:absolute",
39422
+ "relative cursor-text"
39423
+ ),
39424
+ onInput: afterEdit,
39425
+ onKeyDown: handleKeyDown,
39426
+ onBlur: flushEmit
39427
+ }
39428
+ )
39429
+ ] });
39082
39430
  };
39431
+ RichTextEditor.displayName = "RichTextEditor";
39083
39432
  }
39084
- const seed = block.children?.[0]?.content ?? block.content ?? "";
39085
- return { id: block.id, type, content: seed };
39433
+ });
39434
+ function renderIconInput4(icon, props) {
39435
+ return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
39086
39436
  }
39087
- function BlockMenu({ block, readOnly, onDelete, onDuplicate, onChangeType }) {
39437
+ function DocumentPanel({
39438
+ entity,
39439
+ title,
39440
+ subtitle,
39441
+ value,
39442
+ actions,
39443
+ maxInlineActions,
39444
+ editing = false,
39445
+ titleCommitEvent,
39446
+ contentChangeEvent,
39447
+ editEvent,
39448
+ doneEvent,
39449
+ editLabel,
39450
+ doneLabel,
39451
+ autosaveHint,
39452
+ placeholder,
39453
+ className
39454
+ }) {
39455
+ const eventBus = useEventBus();
39088
39456
  const { t } = hooks.useTranslate();
39089
- const [open, setOpen] = React94.useState(false);
39090
- const ref = React94.useRef(null);
39091
- React94.useEffect(() => {
39092
- if (!open) return;
39093
- function onDocClick(e) {
39094
- if (ref.current && !ref.current.contains(e.target)) {
39095
- setOpen(false);
39096
- }
39457
+ const [titleDraft, setTitleDraft] = React94.useState(null);
39458
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
39459
+ const actionDefs = actions ?? [];
39460
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
39461
+ const inlineActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
39462
+ const menuActions = actionDefs.filter((a) => !inlineActions.includes(a));
39463
+ const fireAction = (action) => {
39464
+ if (!action.event) return;
39465
+ eventBus.emit(`UI:${action.event}`, { id: recordId, row: entity });
39466
+ };
39467
+ const commitTitle = () => {
39468
+ if (!titleCommitEvent) return;
39469
+ const next = (titleDraft ?? "").trim();
39470
+ setTitleDraft(null);
39471
+ if (!next || next === title) return;
39472
+ eventBus.emit(`UI:${titleCommitEvent}`, { title: next, id: recordId });
39473
+ };
39474
+ const untitled = t("documentPanel.untitled") || "Untitled";
39475
+ const titleNode = titleCommitEvent && titleDraft !== null ? /* @__PURE__ */ jsxRuntime.jsx(
39476
+ Input,
39477
+ {
39478
+ value: titleDraft,
39479
+ autoFocus: true,
39480
+ "aria-label": t("common.title"),
39481
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
39482
+ onFocus: (e) => e.currentTarget.select(),
39483
+ onChange: (e) => setTitleDraft(e.target.value),
39484
+ onBlur: commitTitle,
39485
+ onKeyDown: (e) => {
39486
+ if (e.key === "Enter") {
39487
+ e.preventDefault();
39488
+ commitTitle();
39489
+ } else if (e.key === "Escape") {
39490
+ e.preventDefault();
39491
+ setTitleDraft(null);
39492
+ }
39493
+ },
39494
+ "data-testid": "document-title-input"
39097
39495
  }
39098
- document.addEventListener("mousedown", onDocClick);
39099
- return () => document.removeEventListener("mousedown", onDocClick);
39100
- }, [open]);
39101
- if (readOnly) return null;
39102
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { ref, className: "relative", children: [
39103
- /* @__PURE__ */ jsxRuntime.jsx(
39104
- Button,
39496
+ ) : titleCommitEvent ? /* @__PURE__ */ jsxRuntime.jsx(
39497
+ Box,
39498
+ {
39499
+ role: "button",
39500
+ tabIndex: 0,
39501
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
39502
+ onClick: () => setTitleDraft(title ?? ""),
39503
+ onKeyDown: (e) => {
39504
+ if (e.key === "Enter" || e.key === " ") {
39505
+ e.preventDefault();
39506
+ setTitleDraft(title ?? "");
39507
+ }
39508
+ },
39509
+ "data-testid": "document-title-editable",
39510
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", weight: "bold", className: cn(!title && "text-muted-foreground"), children: title || untitled })
39511
+ }
39512
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", weight: "bold", children: title || untitled });
39513
+ const emitWithId = (event) => () => {
39514
+ if (event) eventBus.emit(`UI:${event}`, { id: recordId });
39515
+ };
39516
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
39517
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
39518
+ /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
39519
+ titleNode,
39520
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
39521
+ ] }),
39522
+ /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39523
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
39524
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
39525
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39526
+ editEvent && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
39527
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
39528
+ editLabel ?? (t("documentPanel.edit") || "Edit")
39529
+ ] }),
39530
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
39531
+ Button,
39532
+ {
39533
+ variant: "primary",
39534
+ size: "sm",
39535
+ onClick: () => fireAction(action),
39536
+ "data-testid": `action-${action.event}`,
39537
+ children: [
39538
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
39539
+ action.label
39540
+ ]
39541
+ },
39542
+ idx
39543
+ )),
39544
+ menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
39545
+ Menu,
39546
+ {
39547
+ position: "bottom-end",
39548
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
39549
+ items: menuActions.map((action) => ({
39550
+ label: action.label,
39551
+ icon: action.icon,
39552
+ variant: action.variant === "danger" ? "danger" : "default",
39553
+ onClick: () => fireAction(action)
39554
+ }))
39555
+ }
39556
+ )
39557
+ ] }) })
39558
+ ] }),
39559
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
39560
+ Box,
39105
39561
  {
39106
- type: "button",
39107
- variant: "ghost",
39108
- "aria-label": t("richBlockEditor.blockActions"),
39562
+ onClick: emitWithId(editEvent),
39109
39563
  className: cn(
39110
- "inline-flex items-center justify-center",
39111
- "h-6 w-6 rounded-sm p-0 gap-0",
39112
- "text-muted-foreground hover:bg-muted hover:text-foreground",
39113
- "opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
39114
- "transition-opacity"
39564
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
39565
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
39115
39566
  ),
39116
- onClick: () => setOpen((v) => !v),
39117
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", className: "w-3.5 h-3.5" })
39118
- }
39119
- ),
39120
- open && /* @__PURE__ */ jsxRuntime.jsxs(
39121
- Box,
39122
- {
39123
- role: "menu",
39124
- className: cn(
39125
- "absolute right-0 z-10 mt-1 w-44",
39126
- "rounded-container border border-border bg-popover shadow-elevation-popover",
39127
- "py-1 text-sm"
39128
- ),
39129
- children: [
39130
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t(BLOCK_TYPE_LABEL_KEY[block.type]) }),
39131
- /* @__PURE__ */ jsxRuntime.jsxs(
39132
- Button,
39133
- {
39134
- type: "button",
39135
- variant: "ghost",
39136
- role: "menuitem",
39137
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
39138
- onClick: () => {
39139
- onDuplicate();
39140
- setOpen(false);
39141
- },
39142
- children: [
39143
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", className: "w-3.5 h-3.5" }),
39144
- " ",
39145
- t("richBlockEditor.duplicate")
39146
- ]
39147
- }
39148
- ),
39149
- /* @__PURE__ */ jsxRuntime.jsxs(
39150
- Button,
39151
- {
39152
- type: "button",
39153
- variant: "ghost",
39154
- role: "menuitem",
39155
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-destructive hover:bg-muted justify-start rounded-none",
39156
- onClick: () => {
39157
- onDelete();
39158
- setOpen(false);
39159
- },
39160
- children: [
39161
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "trash", className: "w-3.5 h-3.5" }),
39162
- " ",
39163
- t("common.delete")
39164
- ]
39165
- }
39166
- ),
39167
- CHANGEABLE_TYPES.includes(block.type) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39168
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "my-1 border-t border-border" }),
39169
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t("richBlockEditor.turnInto") }),
39170
- CHANGEABLE_TYPES.filter((bt) => bt !== block.type).map((bt) => /* @__PURE__ */ jsxRuntime.jsx(
39171
- Button,
39172
- {
39173
- type: "button",
39174
- variant: "ghost",
39175
- role: "menuitem",
39176
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
39177
- onClick: () => {
39178
- onChangeType(bt);
39179
- setOpen(false);
39180
- },
39181
- children: t(BLOCK_TYPE_LABEL_KEY[bt])
39182
- },
39183
- bt
39184
- ))
39185
- ] })
39186
- ]
39567
+ "data-testid": "document-body",
39568
+ children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
39187
39569
  }
39188
39570
  )
39189
- ] });
39571
+ ] }) });
39190
39572
  }
39191
- function Editable({
39192
- tag,
39193
- value,
39194
- readOnly,
39195
- placeholder,
39196
- className,
39197
- ariaLabel,
39198
- onValueChange
39199
- }) {
39200
- const ref = React94.useRef(null);
39201
- React94.useEffect(() => {
39202
- const el = ref.current;
39203
- if (!el) return;
39204
- const isFocused = document.activeElement === el;
39205
- if (!isFocused && el.textContent !== value) {
39206
- el.textContent = value;
39207
- }
39208
- }, [value]);
39209
- const handleInput = React94.useCallback(
39210
- (e) => {
39211
- onValueChange(e.currentTarget.textContent ?? "");
39212
- },
39213
- [onValueChange]
39214
- );
39215
- return /* @__PURE__ */ jsxRuntime.jsx(
39216
- Box,
39217
- {
39218
- as: tag,
39219
- ref,
39220
- contentEditable: !readOnly,
39221
- suppressContentEditableWarning: true,
39222
- role: readOnly ? void 0 : "textbox",
39223
- "aria-label": ariaLabel,
39224
- "aria-multiline": "true",
39225
- "data-placeholder": placeholder,
39226
- className: cn(
39227
- "outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-sm",
39228
- "empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground/60",
39229
- className
39230
- ),
39231
- onInput: handleInput
39232
- }
39233
- );
39573
+ var init_DocumentPanel = __esm({
39574
+ "components/core/molecules/DocumentPanel.tsx"() {
39575
+ "use client";
39576
+ init_cn();
39577
+ init_useEventBus();
39578
+ init_Box();
39579
+ init_Stack();
39580
+ init_Typography();
39581
+ init_Button();
39582
+ init_Card();
39583
+ init_Icon();
39584
+ init_Input();
39585
+ init_Menu();
39586
+ init_RichTextEditor();
39587
+ }
39588
+ });
39589
+ function renderIconInput5(icon, props) {
39590
+ return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
39234
39591
  }
39235
- function BlockRow({
39236
- block,
39237
- readOnly,
39238
- showAffordances,
39239
- placeholder,
39240
- onUpdate,
39241
- onDelete,
39242
- onDuplicate,
39243
- onChangeType
39592
+ function fieldName(field) {
39593
+ return field.name ?? field.key ?? "";
39594
+ }
39595
+ function relationLabel(value) {
39596
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
39597
+ for (const key of ["name", "title", "label"]) {
39598
+ const candidate = value[key];
39599
+ if (typeof candidate === "string" && candidate !== "") return candidate;
39600
+ }
39601
+ const id = value.id;
39602
+ return id !== void 0 && id !== null ? String(id) : null;
39603
+ }
39604
+ function fieldKind(field, value) {
39605
+ if (field.kind) return field.kind;
39606
+ if (field.options && field.options.length > 0) return "select";
39607
+ if (typeof value === "boolean" || field.format === "boolean") return "boolean";
39608
+ if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
39609
+ return "text";
39610
+ }
39611
+ function DocumentDetails({
39612
+ entity,
39613
+ fields,
39614
+ metaCommitEvent,
39615
+ title,
39616
+ className
39244
39617
  }) {
39618
+ const eventBus = useEventBus();
39245
39619
  const { t } = hooks.useTranslate();
39246
- const setContent = React94.useCallback(
39247
- (next) => onUpdate((b) => ({ ...b, content: next })),
39248
- [onUpdate]
39249
- );
39250
- const setMetadata = React94.useCallback(
39251
- (key, value) => onUpdate((b) => ({
39252
- ...b,
39253
- metadata: { ...b.metadata ?? {}, [key]: value }
39254
- })),
39255
- [onUpdate]
39256
- );
39257
- const setChildContent = React94.useCallback(
39258
- (childId, next) => onUpdate((b) => ({
39259
- ...b,
39260
- children: (b.children ?? []).map(
39261
- (c) => c.id === childId ? { ...c, content: next } : c
39262
- )
39263
- })),
39264
- [onUpdate]
39265
- );
39266
- const addListItem = React94.useCallback(
39267
- () => onUpdate((b) => ({
39268
- ...b,
39269
- children: [
39270
- ...b.children ?? [],
39271
- { id: nextBlockId("li"), type: "paragraph", content: "" }
39272
- ]
39273
- })),
39274
- [onUpdate]
39275
- );
39276
- const removeListItem = React94.useCallback(
39277
- (childId) => onUpdate((b) => {
39278
- const remaining = (b.children ?? []).filter((c) => c.id !== childId);
39279
- return {
39280
- ...b,
39281
- children: remaining.length === 0 ? [{ id: nextBlockId("li"), type: "paragraph", content: "" }] : remaining
39282
- };
39283
- }),
39284
- [onUpdate]
39285
- );
39286
- const renderBody = () => {
39287
- switch (block.type) {
39288
- case "heading-1":
39289
- return /* @__PURE__ */ jsxRuntime.jsx(
39290
- Editable,
39291
- {
39292
- tag: "h1",
39293
- value: block.content ?? "",
39294
- readOnly,
39295
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading1"),
39296
- ariaLabel: t("richBlockEditor.aria.heading1Block"),
39297
- className: "text-3xl font-bold leading-tight",
39298
- onValueChange: setContent
39299
- }
39300
- );
39301
- case "heading-2":
39302
- return /* @__PURE__ */ jsxRuntime.jsx(
39303
- Editable,
39304
- {
39305
- tag: "h2",
39306
- value: block.content ?? "",
39307
- readOnly,
39308
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading2"),
39309
- ariaLabel: t("richBlockEditor.aria.heading2Block"),
39310
- className: "text-2xl font-semibold leading-tight",
39311
- onValueChange: setContent
39312
- }
39313
- );
39314
- case "heading-3":
39315
- return /* @__PURE__ */ jsxRuntime.jsx(
39316
- Editable,
39317
- {
39318
- tag: "h3",
39319
- value: block.content ?? "",
39320
- readOnly,
39321
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading3"),
39322
- ariaLabel: t("richBlockEditor.aria.heading3Block"),
39323
- className: "text-xl font-semibold leading-tight",
39324
- onValueChange: setContent
39325
- }
39326
- );
39327
- case "quote":
39328
- return /* @__PURE__ */ jsxRuntime.jsx(
39329
- Editable,
39330
- {
39331
- tag: "blockquote",
39332
- value: block.content ?? "",
39333
- readOnly,
39334
- placeholder: placeholder ?? t("richBlockEditor.placeholder.quote"),
39335
- ariaLabel: t("richBlockEditor.aria.quoteBlock"),
39336
- className: "border-l-4 border-primary/60 pl-4 italic text-muted-foreground",
39337
- onValueChange: setContent
39338
- }
39339
- );
39340
- case "code":
39341
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "rounded-md border border-border bg-muted/40", children: [
39342
- /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center justify-between border-b border-border px-3 py-1 text-xs text-muted-foreground", children: [
39343
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { as: "span", variant: "caption", className: "uppercase tracking-wide", children: t("richBlockEditor.blockType.code") }),
39344
- !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
39345
- Input,
39346
- {
39347
- inputType: "text",
39348
- value: String(block.metadata?.language ?? "plaintext"),
39349
- "aria-label": t("richBlockEditor.aria.codeLanguage"),
39350
- className: cn(
39351
- "h-6 w-32 rounded-sm border border-border bg-background",
39352
- "px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
39353
- ),
39354
- onChange: (e) => setMetadata("language", e.target.value)
39355
- }
39356
- ),
39357
- readOnly && /* @__PURE__ */ jsxRuntime.jsx(Typography, { as: "span", variant: "caption", className: "text-xs", children: String(block.metadata?.language ?? "plaintext") })
39358
- ] }),
39359
- /* @__PURE__ */ jsxRuntime.jsx(
39360
- Editable,
39361
- {
39362
- tag: "pre",
39363
- value: block.content ?? "",
39364
- readOnly,
39365
- placeholder: placeholder ?? t("richBlockEditor.placeholder.code"),
39366
- ariaLabel: t("richBlockEditor.aria.codeBlock"),
39367
- className: "block whitespace-pre-wrap p-3 font-mono text-sm leading-relaxed",
39368
- onValueChange: setContent
39369
- }
39370
- )
39371
- ] });
39372
- case "divider":
39373
- return /* @__PURE__ */ jsxRuntime.jsx(Divider, { className: "my-2" });
39374
- case "image": {
39375
- const url = String(block.metadata?.url ?? "");
39376
- const caption = String(block.metadata?.caption ?? "");
39377
- const imgProps = {
39378
- src: url,
39379
- alt: caption || t("richBlockEditor.embeddedImage"),
39380
- className: "max-h-96 w-full rounded-md border border-border object-contain"
39381
- };
39382
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "space-y-2", children: [
39383
- url ? /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "img", ...imgProps }) : /* @__PURE__ */ jsxRuntime.jsxs(
39384
- Box,
39385
- {
39386
- className: cn(
39387
- "flex h-32 items-center justify-center",
39388
- "rounded-md border border-dashed border-border",
39389
- "text-sm text-muted-foreground"
39390
- ),
39391
- children: [
39392
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "image", className: "mr-2 w-4 h-4" }),
39393
- " ",
39394
- t("richBlockEditor.noImageUrl")
39395
- ]
39396
- }
39397
- ),
39398
- !readOnly && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-col gap-2 sm:flex-row", children: [
39399
- /* @__PURE__ */ jsxRuntime.jsx(
39400
- Input,
39401
- {
39402
- inputType: "url",
39403
- value: url,
39404
- placeholder: "https://example.com/image.png",
39405
- "aria-label": t("richBlockEditor.aria.imageUrl"),
39406
- className: cn(
39407
- "h-8 flex-1 rounded-sm border border-border bg-background",
39408
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
39409
- ),
39410
- onChange: (e) => setMetadata("url", e.target.value)
39411
- }
39412
- ),
39413
- /* @__PURE__ */ jsxRuntime.jsx(
39414
- Input,
39415
- {
39416
- inputType: "text",
39417
- value: caption,
39418
- placeholder: t("richBlockEditor.placeholder.caption"),
39419
- "aria-label": t("richBlockEditor.aria.imageCaption"),
39420
- className: cn(
39421
- "h-8 flex-1 rounded-sm border border-border bg-background",
39422
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
39423
- ),
39424
- onChange: (e) => setMetadata("caption", e.target.value)
39425
- }
39426
- )
39427
- ] }),
39428
- readOnly && caption && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-center text-muted-foreground", children: caption })
39429
- ] });
39620
+ const [fieldDraft, setFieldDraft] = React94.useState(null);
39621
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
39622
+ const fieldDefs = (fields ?? []).filter((f3) => fieldName(f3) !== "");
39623
+ if (fieldDefs.length === 0) return null;
39624
+ const commitField = (name, next, current) => {
39625
+ setFieldDraft(null);
39626
+ if (!metaCommitEvent || next === current) return;
39627
+ eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
39628
+ };
39629
+ const renderValue = (field) => {
39630
+ const name = fieldName(field);
39631
+ const raw = getNestedValue(entity ?? {}, name);
39632
+ const kind = fieldKind(field, raw);
39633
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
39634
+ if (kind === "boolean") {
39635
+ return /* @__PURE__ */ jsxRuntime.jsx(
39636
+ Switch,
39637
+ {
39638
+ checked: Boolean(raw),
39639
+ disabled: !metaCommitEvent,
39640
+ "aria-label": label,
39641
+ onChange: (checked) => commitField(name, checked, Boolean(raw))
39642
+ }
39643
+ );
39644
+ }
39645
+ if (kind === "select") {
39646
+ return /* @__PURE__ */ jsxRuntime.jsx(
39647
+ Select,
39648
+ {
39649
+ value: raw !== void 0 && raw !== null ? String(raw) : "",
39650
+ disabled: !metaCommitEvent,
39651
+ "aria-label": label,
39652
+ className: "h-8 w-full",
39653
+ options: (field.options ?? []).map((opt) => ({ value: opt, label: humanizeEnumValue(opt) })),
39654
+ onChange: (e) => commitField(name, e.target.value, raw)
39655
+ }
39656
+ );
39657
+ }
39658
+ if (kind === "readonly" || !metaCommitEvent) {
39659
+ if (Array.isArray(raw)) {
39660
+ if (raw.length === 0) {
39661
+ return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
39662
+ }
39663
+ return /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
39430
39664
  }
39431
- case "bullet-list":
39432
- case "numbered-list": {
39433
- const items = block.children ?? [];
39434
- return /* @__PURE__ */ jsxRuntime.jsxs(
39435
- Box,
39436
- {
39437
- as: block.type === "bullet-list" ? "ul" : "ol",
39438
- className: cn(
39439
- "space-y-1 pl-6",
39440
- block.type === "bullet-list" ? "list-disc" : "list-decimal"
39441
- ),
39442
- children: [
39443
- items.map((child) => /* @__PURE__ */ jsxRuntime.jsxs(Box, { as: "li", className: "group/item flex items-start gap-2", children: [
39444
- /* @__PURE__ */ jsxRuntime.jsx(
39445
- Editable,
39446
- {
39447
- tag: "span",
39448
- value: child.content ?? "",
39449
- readOnly,
39450
- placeholder: t("richBlockEditor.placeholder.listItem"),
39451
- ariaLabel: t("richBlockEditor.aria.listItem"),
39452
- className: "inline-block min-w-[1ch] flex-1",
39453
- onValueChange: (next) => setChildContent(child.id, next)
39454
- }
39455
- ),
39456
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(
39457
- Button,
39458
- {
39459
- type: "button",
39460
- variant: "ghost",
39461
- "aria-label": t("richBlockEditor.aria.removeListItem"),
39462
- className: cn(
39463
- "h-5 w-5 shrink-0 rounded-sm text-muted-foreground p-0 gap-0",
39464
- "opacity-0 group-hover/item:opacity-100 hover:bg-muted hover:text-foreground"
39465
- ),
39466
- onClick: () => removeListItem(child.id),
39467
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "trash", className: "w-3 h-3" })
39468
- }
39469
- )
39470
- ] }, child.id)),
39471
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "li", className: "list-none pl-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
39472
- Button,
39473
- {
39474
- type: "button",
39475
- variant: "ghost",
39476
- className: cn(
39477
- "inline-flex items-center gap-1 text-xs text-muted-foreground",
39478
- "hover:text-foreground p-0 h-auto"
39479
- ),
39480
- onClick: addListItem,
39481
- children: [
39482
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", className: "w-3 h-3" }),
39483
- " ",
39484
- t("richBlockEditor.addItem")
39485
- ]
39486
- }
39487
- ) })
39488
- ]
39665
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
39666
+ return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
39667
+ }
39668
+ if (fieldDraft?.name === name) {
39669
+ return /* @__PURE__ */ jsxRuntime.jsx(
39670
+ Input,
39671
+ {
39672
+ value: fieldDraft.value,
39673
+ autoFocus: true,
39674
+ "aria-label": label,
39675
+ className: "h-8 w-full",
39676
+ onChange: (e) => setFieldDraft({ name, value: e.target.value }),
39677
+ onBlur: () => commitField(name, fieldDraft.value.trim(), raw),
39678
+ onKeyDown: (e) => {
39679
+ if (e.key === "Enter") {
39680
+ e.preventDefault();
39681
+ commitField(name, fieldDraft.value.trim(), raw);
39682
+ } else if (e.key === "Escape") {
39683
+ e.preventDefault();
39684
+ setFieldDraft(null);
39685
+ }
39686
+ },
39687
+ "data-testid": `document-property-input-${name}`
39688
+ }
39689
+ );
39690
+ }
39691
+ const shown = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
39692
+ return /* @__PURE__ */ jsxRuntime.jsx(
39693
+ Box,
39694
+ {
39695
+ role: "button",
39696
+ tabIndex: 0,
39697
+ className: cn(
39698
+ "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
39699
+ (raw === void 0 || raw === null || raw === "") && "text-muted-foreground"
39700
+ ),
39701
+ onClick: () => setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" }),
39702
+ onKeyDown: (e) => {
39703
+ if (e.key === "Enter" || e.key === " ") {
39704
+ e.preventDefault();
39705
+ setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" });
39489
39706
  }
39490
- );
39707
+ },
39708
+ "data-testid": `document-property-${name}`,
39709
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "break-words", children: shown })
39491
39710
  }
39492
- case "paragraph":
39493
- default:
39494
- return /* @__PURE__ */ jsxRuntime.jsx(
39495
- Editable,
39496
- {
39497
- tag: "p",
39498
- value: block.content ?? "",
39499
- readOnly,
39500
- placeholder: placeholder ?? t("richBlockEditor.placeholder.paragraph"),
39501
- ariaLabel: t("richBlockEditor.aria.paragraphBlock"),
39502
- className: "leading-7",
39503
- onValueChange: setContent
39504
- }
39505
- );
39506
- }
39711
+ );
39507
39712
  };
39508
- return /* @__PURE__ */ jsxRuntime.jsxs(
39509
- Box,
39510
- {
39511
- className: cn(
39512
- "group relative flex items-start gap-2 rounded-sm",
39513
- "px-2 py-1 hover:bg-muted/30"
39514
- ),
39515
- "data-block-id": block.id,
39516
- "data-block-type": block.type,
39517
- children: [
39518
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex w-8 shrink-0 items-center pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(
39519
- BlockMenu,
39520
- {
39521
- block,
39522
- readOnly,
39523
- onDelete,
39524
- onDuplicate,
39525
- onChangeType
39526
- }
39527
- ) }),
39528
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "min-w-0 flex-1", children: renderBody() })
39529
- ]
39530
- }
39531
- );
39713
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { variant: "bordered", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: "p-4", children: [
39714
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", weight: "medium", className: "uppercase tracking-wide", children: title ?? (t("documentDetails.title") || "Details") }),
39715
+ /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", children: fieldDefs.map((field) => {
39716
+ const name = fieldName(field);
39717
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
39718
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
39719
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
39720
+ field.icon && renderIconInput5(field.icon, { size: "xs", className: "text-muted-foreground" }),
39721
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", children: label })
39722
+ ] }),
39723
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "min-w-0", children: renderValue(field) })
39724
+ ] }, name);
39725
+ }) })
39726
+ ] }) });
39532
39727
  }
39533
- var TOOLBAR_ENTRIES, BLOCK_TYPE_LABEL_KEY, CHANGEABLE_TYPES, _idSeq, BLOCK_TYPES, RichBlockEditor;
39534
- var init_RichBlockEditor = __esm({
39535
- "components/core/molecules/RichBlockEditor.tsx"() {
39728
+ var init_DocumentDetails = __esm({
39729
+ "components/core/molecules/DocumentDetails.tsx"() {
39536
39730
  "use client";
39537
39731
  init_cn();
39538
- init_Card();
39539
- init_Typography();
39540
- init_Button();
39732
+ init_format();
39733
+ init_getNestedValue();
39734
+ init_useEventBus();
39541
39735
  init_Box();
39542
- init_Divider();
39543
- init_Input();
39736
+ init_Stack();
39737
+ init_Typography();
39738
+ init_Badge();
39739
+ init_Card();
39544
39740
  init_Icon();
39545
- init_useEventBus();
39546
- TOOLBAR_ENTRIES = [
39547
- { type: "paragraph", labelKey: "richBlockEditor.toolbar.text", icon: LucideIcons2.Type },
39548
- { type: "heading-1", labelKey: "richBlockEditor.toolbar.h1", icon: LucideIcons2.Heading1 },
39549
- { type: "heading-2", labelKey: "richBlockEditor.toolbar.h2", icon: LucideIcons2.Heading2 },
39550
- { type: "heading-3", labelKey: "richBlockEditor.toolbar.h3", icon: LucideIcons2.Heading3 },
39551
- { type: "bullet-list", labelKey: "richBlockEditor.toolbar.bulletList", icon: LucideIcons2.List },
39552
- { type: "numbered-list", labelKey: "richBlockEditor.toolbar.numbered", icon: LucideIcons2.ListOrdered },
39553
- { type: "quote", labelKey: "richBlockEditor.toolbar.quote", icon: LucideIcons2.Quote },
39554
- { type: "code", labelKey: "richBlockEditor.toolbar.code", icon: LucideIcons2.Code },
39555
- { type: "divider", labelKey: "richBlockEditor.toolbar.divider", icon: LucideIcons2.Minus },
39556
- { type: "image", labelKey: "richBlockEditor.toolbar.image", icon: LucideIcons2.Image }
39557
- ];
39558
- BLOCK_TYPE_LABEL_KEY = {
39559
- paragraph: "richBlockEditor.blockType.paragraph",
39560
- "heading-1": "richBlockEditor.blockType.heading1",
39561
- "heading-2": "richBlockEditor.blockType.heading2",
39562
- "heading-3": "richBlockEditor.blockType.heading3",
39563
- "bullet-list": "richBlockEditor.blockType.bulletList",
39564
- "numbered-list": "richBlockEditor.blockType.numberedList",
39565
- quote: "richBlockEditor.blockType.quote",
39566
- code: "richBlockEditor.blockType.code",
39567
- divider: "richBlockEditor.blockType.divider",
39568
- image: "richBlockEditor.blockType.image"
39569
- };
39570
- CHANGEABLE_TYPES = [
39571
- "paragraph",
39572
- "heading-1",
39573
- "heading-2",
39574
- "heading-3",
39575
- "bullet-list",
39576
- "numbered-list",
39577
- "quote",
39578
- "code"
39579
- ];
39580
- _idSeq = 0;
39581
- BLOCK_TYPES = /* @__PURE__ */ new Set([
39582
- "paragraph",
39583
- "heading-1",
39584
- "heading-2",
39585
- "heading-3",
39586
- "bullet-list",
39587
- "numbered-list",
39588
- "quote",
39589
- "code",
39590
- "divider",
39591
- "image"
39592
- ]);
39593
- RichBlockEditor = ({
39594
- initialBlocks,
39595
- onChange,
39596
- changeEvent,
39597
- readOnly = false,
39598
- placeholder,
39599
- enableBlocks = true,
39600
- showToolbar = true,
39601
- className
39602
- }) => {
39603
- const { t } = hooks.useTranslate();
39604
- const [blocks, setBlocks] = React94.useState(
39605
- () => normalizeBlocks(initialBlocks)
39606
- );
39607
- const onChangeRef = React94.useRef(onChange);
39608
- React94.useEffect(() => {
39609
- onChangeRef.current = onChange;
39610
- }, [onChange]);
39611
- const eventBus = useEventBus();
39612
- const changeEventRef = React94.useRef(changeEvent);
39613
- React94.useEffect(() => {
39614
- changeEventRef.current = changeEvent;
39615
- }, [changeEvent]);
39616
- const commit = React94.useCallback((next) => {
39617
- setBlocks(next);
39618
- onChangeRef.current?.(next);
39619
- const evt = changeEventRef.current;
39620
- if (evt) eventBus.emit(`UI:${evt}`, { blocks: next });
39621
- }, [eventBus]);
39622
- const handleAppend = React94.useCallback(
39623
- (type) => {
39624
- if (readOnly) return;
39625
- commit([...blocks, createBlock(type)]);
39626
- },
39627
- [blocks, commit, readOnly]
39628
- );
39629
- const handleUpdate = React94.useCallback(
39630
- (id, updater) => {
39631
- commit(replaceBlock(blocks, id, updater));
39632
- },
39633
- [blocks, commit]
39634
- );
39635
- const handleDelete = React94.useCallback(
39636
- (id) => {
39637
- const next = removeBlock(blocks, id);
39638
- commit(next.length > 0 ? next : [createBlock("paragraph")]);
39639
- },
39640
- [blocks, commit]
39641
- );
39642
- const handleDuplicate = React94.useCallback(
39643
- (id) => {
39644
- const target = blocks.find((b) => b.id === id);
39645
- if (!target) return;
39646
- commit(insertAfter(blocks, id, duplicateBlock(target)));
39647
- },
39648
- [blocks, commit]
39649
- );
39650
- const handleChangeType = React94.useCallback(
39651
- (id, type) => {
39652
- commit(
39653
- replaceBlock(blocks, id, (b) => changeBlockType(b, type))
39654
- );
39655
- },
39656
- [blocks, commit]
39657
- );
39658
- return /* @__PURE__ */ jsxRuntime.jsxs(
39659
- Card,
39660
- {
39661
- variant: "bordered",
39662
- padding: "none",
39663
- className: cn("flex flex-col text-card-foreground", className),
39664
- children: [
39665
- enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
39666
- Box,
39667
- {
39668
- role: "toolbar",
39669
- "aria-label": t("richBlockEditor.editorToolbar"),
39670
- className: cn(
39671
- "flex flex-wrap items-center gap-1",
39672
- "border-b border-border bg-muted/30 px-2 py-2"
39673
- ),
39674
- children: TOOLBAR_ENTRIES.map((entry) => {
39675
- const Icon2 = entry.icon;
39676
- const entryLabel = t(entry.labelKey);
39677
- return /* @__PURE__ */ jsxRuntime.jsxs(
39678
- Button,
39679
- {
39680
- type: "button",
39681
- variant: "ghost",
39682
- size: "sm",
39683
- "aria-label": t("richBlockEditor.insertEntry", { label: entryLabel }),
39684
- title: entryLabel,
39685
- onClick: () => handleAppend(entry.type),
39686
- children: [
39687
- /* @__PURE__ */ jsxRuntime.jsx(Icon2, { size: 14 }),
39688
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { as: "span", variant: "caption", className: "ml-1 hidden text-xs sm:inline", children: entryLabel })
39689
- ]
39690
- },
39691
- entry.type
39692
- );
39693
- })
39694
- }
39695
- ),
39696
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex flex-col gap-1 px-3 py-3", children: blocks.map((block) => /* @__PURE__ */ jsxRuntime.jsx(
39697
- BlockRow,
39698
- {
39699
- block,
39700
- readOnly,
39701
- showAffordances: enableBlocks,
39702
- placeholder,
39703
- onUpdate: (updater) => handleUpdate(block.id, updater),
39704
- onDelete: () => handleDelete(block.id),
39705
- onDuplicate: () => handleDuplicate(block.id),
39706
- onChangeType: (type) => handleChangeType(block.id, type)
39707
- },
39708
- block.id
39709
- )) })
39710
- ]
39711
- }
39712
- );
39713
- };
39714
- RichBlockEditor.displayName = "RichBlockEditor";
39741
+ init_Input();
39742
+ init_Select();
39743
+ init_Switch();
39715
39744
  }
39716
39745
  });
39717
39746
  function collectInitiallyCollapsed(nodes, acc) {
@@ -44251,8 +44280,8 @@ var init_DataTable = __esm({
44251
44280
  DataTable.displayName = "DataTable";
44252
44281
  }
44253
44282
  });
44254
- function getBadgeVariant(fieldName, value) {
44255
- const name = fieldName.toLowerCase();
44283
+ function getBadgeVariant(fieldName2, value) {
44284
+ const name = fieldName2.toLowerCase();
44256
44285
  const val = String(value).toLowerCase();
44257
44286
  if (name.includes("status")) {
44258
44287
  if (val.includes("complete") || val.includes("done") || val.includes("active"))
@@ -44268,12 +44297,12 @@ function getBadgeVariant(fieldName, value) {
44268
44297
  }
44269
44298
  return "default";
44270
44299
  }
44271
- function formatFieldValue2(value, fieldName) {
44300
+ function formatFieldValue2(value, fieldName2) {
44272
44301
  if (typeof value === "number") {
44273
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
44302
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
44274
44303
  return `${value}%`;
44275
44304
  }
44276
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
44305
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
44277
44306
  return `$${value.toLocaleString()}`;
44278
44307
  }
44279
44308
  return value.toLocaleString();
@@ -44283,7 +44312,7 @@ function formatFieldValue2(value, fieldName) {
44283
44312
  }
44284
44313
  return String(value);
44285
44314
  }
44286
- function renderRichFieldValue(value, fieldName, fieldType, meta) {
44315
+ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
44287
44316
  if (value === void 0 || value === null) return "\u2014";
44288
44317
  const str = String(value);
44289
44318
  switch (fieldType) {
@@ -44294,7 +44323,7 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
44294
44323
  "img",
44295
44324
  {
44296
44325
  src: str,
44297
- alt: formatFieldLabel(fieldName),
44326
+ alt: formatFieldLabel(fieldName2),
44298
44327
  className: "max-w-full max-h-64 rounded-md object-contain",
44299
44328
  loading: "lazy"
44300
44329
  }
@@ -44429,9 +44458,9 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
44429
44458
  return /* @__PURE__ */ jsxRuntime.jsx("a", { href: `tel:${str}`, className: "text-primary hover:underline", children: str });
44430
44459
  default:
44431
44460
  if (meta?.values && meta.values.length > 0 && meta.values.includes(str)) {
44432
- return /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: getBadgeVariant(fieldName, str), children: humanizeEnumValue(str) });
44461
+ return /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: getBadgeVariant(fieldName2, str), children: humanizeEnumValue(str) });
44433
44462
  }
44434
- return formatFieldValue2(value, fieldName);
44463
+ return formatFieldValue2(value, fieldName2);
44435
44464
  }
44436
44465
  }
44437
44466
  function normalizeFieldDefs(fields) {
@@ -44901,16 +44930,11 @@ var init_DetailPanel = __esm({
44901
44930
  footer
44902
44931
  ] })
44903
44932
  ] }) });
44904
- return /* @__PURE__ */ jsxRuntime.jsx(
44905
- Box,
44906
- {
44907
- className: cn(
44908
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
44909
- className
44910
- ),
44911
- children: content
44912
- }
44913
- );
44933
+ if (!slideOver) {
44934
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className, children: content });
44935
+ }
44936
+ const panel = /* @__PURE__ */ jsxRuntime.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 });
44937
+ return typeof document === "undefined" ? panel : reactDom.createPortal(panel, document.body);
44914
44938
  };
44915
44939
  DetailPanel.displayName = "DetailPanel";
44916
44940
  }
@@ -45279,7 +45303,21 @@ var init_Form = __esm({
45279
45303
  const normalizedInitialData = React94__namespace.default.useMemo(() => {
45280
45304
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
45281
45305
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
45282
- return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
45306
+ const merged = entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
45307
+ const toId = (value) => {
45308
+ if (value === null || value === void 0 || typeof value !== "object" || value instanceof Date) return value;
45309
+ if (Array.isArray(value)) {
45310
+ return value.map(
45311
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && item.id !== void 0 && item.id !== null ? String(item.id) : item
45312
+ );
45313
+ }
45314
+ return value.id !== void 0 && value.id !== null ? String(value.id) : value;
45315
+ };
45316
+ const normalized = {};
45317
+ for (const [key, value] of Object.entries(merged)) {
45318
+ normalized[key] = key === "id" ? value : toId(value);
45319
+ }
45320
+ return normalized;
45283
45321
  }, [entity, initialData]);
45284
45322
  const entityDerivedFields = React94__namespace.default.useMemo(() => {
45285
45323
  if (fields && fields.length > 0) return void 0;
@@ -45408,8 +45446,8 @@ var init_Form = __esm({
45408
45446
  checkViolations(name, newFormData);
45409
45447
  };
45410
45448
  const isFieldVisible = React94__namespace.default.useCallback(
45411
- (fieldName) => {
45412
- const condition = conditionalFields[fieldName];
45449
+ (fieldName2) => {
45450
+ const condition = conditionalFields[fieldName2];
45413
45451
  if (!condition) return true;
45414
45452
  return Boolean(evaluateFormExpression(condition, evalContext));
45415
45453
  },
@@ -45455,9 +45493,9 @@ var init_Form = __esm({
45455
45493
  };
45456
45494
  const handleInvalid = (e) => {
45457
45495
  const target = e.target;
45458
- const fieldName = target.getAttribute("data-field-name") ?? target.name ?? "";
45496
+ const fieldName2 = target.getAttribute("data-field-name") ?? target.name ?? "";
45459
45497
  const fieldMessage = target.validationMessage || "Invalid value";
45460
- debug("forms", "invalid", { mode: formMode, fieldName, fieldMessage });
45498
+ debug("forms", "invalid", { mode: formMode, fieldName: fieldName2, fieldMessage });
45461
45499
  queueMicrotask(() => {
45462
45500
  const form = formRef.current;
45463
45501
  if (!form) return;
@@ -45493,22 +45531,22 @@ var init_Form = __esm({
45493
45531
  };
45494
45532
  const renderField = React94__namespace.default.useCallback(
45495
45533
  (field) => {
45496
- const fieldName = field.name || field.field;
45497
- if (!fieldName) return null;
45498
- if (!isFieldVisible(fieldName)) {
45534
+ const fieldName2 = field.name || field.field;
45535
+ if (!fieldName2) return null;
45536
+ if (!isFieldVisible(fieldName2)) {
45499
45537
  return null;
45500
45538
  }
45501
45539
  const inputType = determineInputType(field);
45502
- const label = field.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, " $1");
45503
- const currentValue = formData[fieldName] ?? field.defaultValue ?? "";
45504
- return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", "data-field": fieldName, children: [
45540
+ const label = field.label || fieldName2.charAt(0).toUpperCase() + fieldName2.slice(1).replace(/([A-Z])/g, " $1");
45541
+ const currentValue = formData[fieldName2] ?? field.defaultValue ?? "";
45542
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", "data-field": fieldName2, children: [
45505
45543
  inputType !== "checkbox" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { as: "label", variant: "label", weight: "bold", children: [
45506
45544
  label,
45507
45545
  field.required && /* @__PURE__ */ jsxRuntime.jsx(Typography, { as: "span", color: "error", className: "ml-1", children: "*" })
45508
45546
  ] }),
45509
- renderFieldInput(field, fieldName, inputType, currentValue, label),
45547
+ renderFieldInput(field, fieldName2, inputType, currentValue, label),
45510
45548
  field.hint && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", children: field.hint })
45511
- ] }, fieldName);
45549
+ ] }, fieldName2);
45512
45550
  },
45513
45551
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
45514
45552
  );
@@ -45537,8 +45575,8 @@ var init_Form = __esm({
45537
45575
  }
45538
45576
  return field;
45539
45577
  }).map((field) => {
45540
- const fieldName = field.name || field.field;
45541
- const override = fieldOverrides?.find((o) => o.name === fieldName);
45578
+ const fieldName2 = field.name || field.field;
45579
+ const override = fieldOverrides?.find((o) => o.name === fieldName2);
45542
45580
  if (!override) return field;
45543
45581
  return {
45544
45582
  ...field,
@@ -45594,11 +45632,11 @@ var init_Form = __esm({
45594
45632
  ] }, section.id);
45595
45633
  }).filter(Boolean);
45596
45634
  }, [sections, isSectionVisible, collapsedSections, renderField, gap]);
45597
- function renderFieldInput(field, fieldName, inputType, currentValue, label) {
45635
+ function renderFieldInput(field, fieldName2, inputType, currentValue, label) {
45598
45636
  const commonProps = {
45599
- id: fieldName,
45600
- name: fieldName,
45601
- "data-field-name": fieldName,
45637
+ id: fieldName2,
45638
+ name: fieldName2,
45639
+ "data-field-name": fieldName2,
45602
45640
  required: field.required,
45603
45641
  disabled: isLoading,
45604
45642
  placeholder: field.placeholder,
@@ -45612,7 +45650,7 @@ var init_Form = __esm({
45612
45650
  ...commonProps,
45613
45651
  label: label + (field.required ? " *" : ""),
45614
45652
  checked: Boolean(currentValue),
45615
- onChange: (e) => handleChange(fieldName, e.target.checked)
45653
+ onChange: (e) => handleChange(fieldName2, e.target.checked)
45616
45654
  }
45617
45655
  );
45618
45656
  case "textarea":
@@ -45621,7 +45659,7 @@ var init_Form = __esm({
45621
45659
  {
45622
45660
  ...commonProps,
45623
45661
  value: String(currentValue),
45624
- onChange: (e) => handleChange(fieldName, e.target.value),
45662
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45625
45663
  minLength: field.min,
45626
45664
  maxLength: field.max
45627
45665
  }
@@ -45634,14 +45672,14 @@ var init_Form = __esm({
45634
45672
  ...commonProps,
45635
45673
  options,
45636
45674
  value: String(currentValue),
45637
- onValueChange: (v) => handleChange(fieldName, v),
45675
+ onValueChange: (v) => handleChange(fieldName2, v),
45638
45676
  placeholder: field.placeholder || `Select ${label}...`
45639
45677
  }
45640
45678
  );
45641
45679
  }
45642
45680
  case "relation": {
45643
- const relationOptions = relationsData[fieldName] || [];
45644
- const relationLoading = relationsLoading[fieldName] || false;
45681
+ const relationOptions = relationsData[fieldName2] || [];
45682
+ const relationLoading = relationsLoading[fieldName2] || false;
45645
45683
  if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
45646
45684
  const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
45647
45685
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -45653,7 +45691,7 @@ var init_Form = __esm({
45653
45691
  clearable: true,
45654
45692
  options: [...relationOptions],
45655
45693
  value: selectedValues,
45656
- onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
45694
+ onValueChange: (value) => handleChange(fieldName2, Array.isArray(value) ? value : [value]),
45657
45695
  placeholder: field.placeholder || `Select ${label}...`
45658
45696
  }
45659
45697
  );
@@ -45663,7 +45701,7 @@ var init_Form = __esm({
45663
45701
  {
45664
45702
  ...commonProps,
45665
45703
  value: currentValue ? String(currentValue) : void 0,
45666
- onChange: (value) => handleChange(fieldName, value),
45704
+ onChange: (value) => handleChange(fieldName2, value),
45667
45705
  options: relationOptions,
45668
45706
  isLoading: relationLoading,
45669
45707
  placeholder: field.placeholder || `Select ${label}...`,
@@ -45680,7 +45718,7 @@ var init_Form = __esm({
45680
45718
  placeholder: field.placeholder,
45681
45719
  disabled: isLoading,
45682
45720
  value: arrayValue,
45683
- onChange: (next) => handleChange(fieldName, [...next])
45721
+ onChange: (next) => handleChange(fieldName2, [...next])
45684
45722
  }
45685
45723
  );
45686
45724
  }
@@ -45692,7 +45730,7 @@ var init_Form = __esm({
45692
45730
  type: "number",
45693
45731
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
45694
45732
  onChange: (e) => handleChange(
45695
- fieldName,
45733
+ fieldName2,
45696
45734
  e.target.value ? Number(e.target.value) : void 0
45697
45735
  ),
45698
45736
  min: field.min,
@@ -45709,7 +45747,7 @@ var init_Form = __esm({
45709
45747
  icon: LucideIcons2.DollarSign,
45710
45748
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
45711
45749
  onChange: (e) => handleChange(
45712
- fieldName,
45750
+ fieldName2,
45713
45751
  e.target.value ? Number(e.target.value) : void 0
45714
45752
  ),
45715
45753
  min: field.min,
@@ -45727,7 +45765,7 @@ var init_Form = __esm({
45727
45765
  const f3 = files[0];
45728
45766
  if (!f3) return;
45729
45767
  const reader = new FileReader();
45730
- reader.onload = () => handleChange(fieldName, {
45768
+ reader.onload = () => handleChange(fieldName2, {
45731
45769
  name: f3.name,
45732
45770
  mimeType: f3.type,
45733
45771
  sizeBytes: f3.size,
@@ -45744,7 +45782,7 @@ var init_Form = __esm({
45744
45782
  ...commonProps,
45745
45783
  type: "date",
45746
45784
  value: formatDateValue(currentValue),
45747
- onChange: (e) => handleChange(fieldName, e.target.value)
45785
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45748
45786
  }
45749
45787
  );
45750
45788
  case "datetime-local":
@@ -45754,7 +45792,7 @@ var init_Form = __esm({
45754
45792
  ...commonProps,
45755
45793
  type: "datetime-local",
45756
45794
  value: formatDateTimeValue(currentValue),
45757
- onChange: (e) => handleChange(fieldName, e.target.value)
45795
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45758
45796
  }
45759
45797
  );
45760
45798
  case "email":
@@ -45764,7 +45802,7 @@ var init_Form = __esm({
45764
45802
  ...commonProps,
45765
45803
  type: "email",
45766
45804
  value: String(currentValue),
45767
- onChange: (e) => handleChange(fieldName, e.target.value),
45805
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45768
45806
  minLength: field.min,
45769
45807
  maxLength: field.max
45770
45808
  }
@@ -45776,7 +45814,7 @@ var init_Form = __esm({
45776
45814
  ...commonProps,
45777
45815
  type: "url",
45778
45816
  value: String(currentValue),
45779
- onChange: (e) => handleChange(fieldName, e.target.value),
45817
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45780
45818
  minLength: field.min,
45781
45819
  maxLength: field.max
45782
45820
  }
@@ -45788,7 +45826,7 @@ var init_Form = __esm({
45788
45826
  ...commonProps,
45789
45827
  type: "password",
45790
45828
  value: String(currentValue),
45791
- onChange: (e) => handleChange(fieldName, e.target.value),
45829
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45792
45830
  minLength: field.min,
45793
45831
  maxLength: field.max
45794
45832
  }
@@ -45801,7 +45839,7 @@ var init_Form = __esm({
45801
45839
  ...commonProps,
45802
45840
  type: "text",
45803
45841
  value: String(currentValue),
45804
- onChange: (e) => handleChange(fieldName, e.target.value),
45842
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45805
45843
  minLength: field.min,
45806
45844
  maxLength: field.max
45807
45845
  }
@@ -46158,7 +46196,7 @@ function entityFieldsFromListItem(item) {
46158
46196
  }
46159
46197
  return result;
46160
46198
  }
46161
- function getStatusStyle(fieldName, value) {
46199
+ function getStatusStyle(fieldName2, value) {
46162
46200
  const val = String(value).toLowerCase();
46163
46201
  if (val.includes("complete") || val.includes("done"))
46164
46202
  return STATUS_STYLES.complete;
@@ -46174,12 +46212,12 @@ function getStatusStyle(fieldName, value) {
46174
46212
  if (val.includes("low")) return STATUS_STYLES.low;
46175
46213
  return STATUS_STYLES.default;
46176
46214
  }
46177
- function formatValue3(value, fieldName) {
46215
+ function formatValue3(value, fieldName2) {
46178
46216
  if (typeof value === "number") {
46179
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
46217
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
46180
46218
  return `${value}%`;
46181
46219
  }
46182
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
46220
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
46183
46221
  return new Intl.NumberFormat("en-US", {
46184
46222
  style: "currency",
46185
46223
  currency: "USD",
@@ -46202,8 +46240,8 @@ function formatValue3(value, fieldName) {
46202
46240
  }
46203
46241
  return String(value);
46204
46242
  }
46205
- function formatFieldLabel2(fieldName) {
46206
- return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
46243
+ function formatFieldLabel2(fieldName2) {
46244
+ return humanizeFieldName(fieldName2).replace(/\sId$/, "").trim();
46207
46245
  }
46208
46246
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
46209
46247
  var init_List = __esm({
@@ -46271,9 +46309,9 @@ var init_List = __esm({
46271
46309
  };
46272
46310
  StatusBadge = ({
46273
46311
  value,
46274
- fieldName
46312
+ fieldName: fieldName2
46275
46313
  }) => {
46276
- const style = getStatusStyle(fieldName, value);
46314
+ const style = getStatusStyle(fieldName2, value);
46277
46315
  return /* @__PURE__ */ jsxRuntime.jsxs(
46278
46316
  Typography,
46279
46317
  {
@@ -49205,8 +49243,8 @@ var init_StatCard = __esm({
49205
49243
  return items.length;
49206
49244
  }
49207
49245
  if (field.includes(":")) {
49208
- const [fieldName, fieldValue] = field.split(":");
49209
- return items.filter((item) => item[fieldName] === fieldValue).length;
49246
+ const [fieldName2, fieldValue] = field.split(":");
49247
+ return items.filter((item) => item[fieldName2] === fieldValue).length;
49210
49248
  }
49211
49249
  const fieldExistsOnItems = items.some((item) => field in item);
49212
49250
  if (fieldExistsOnItems) {
@@ -50726,6 +50764,8 @@ var init_component_registry_generated = __esm({
50726
50764
  init_DocSearch();
50727
50765
  init_DocSidebar();
50728
50766
  init_DocTOC();
50767
+ init_DocumentDetails();
50768
+ init_DocumentPanel();
50729
50769
  init_DocumentViewer();
50730
50770
  init_DrawFxLayer();
50731
50771
  init_DrawGroup();
@@ -50835,7 +50875,7 @@ var init_component_registry_generated = __esm({
50835
50875
  init_RelationSelect();
50836
50876
  init_RepeatableFormSection();
50837
50877
  init_ReplyTree();
50838
- init_RichBlockEditor();
50878
+ init_RichTextEditor();
50839
50879
  init_RuntimeDebugger2();
50840
50880
  init_ScaledDiagram();
50841
50881
  init_ScoreDisplay();
@@ -50998,6 +51038,8 @@ var init_component_registry_generated = __esm({
50998
51038
  "DocSearch": DocSearch,
50999
51039
  "DocSidebar": DocSidebar,
51000
51040
  "DocTOC": DocTOC,
51041
+ "DocumentDetails": DocumentDetails,
51042
+ "DocumentPanel": DocumentPanel,
51001
51043
  "DocumentViewer": DocumentViewer,
51002
51044
  "DrawFxLayer": DrawFxLayer,
51003
51045
  "DrawGroup": DrawGroup,
@@ -51109,7 +51151,7 @@ var init_component_registry_generated = __esm({
51109
51151
  "RelationSelect": RelationSelect,
51110
51152
  "RepeatableFormSection": RepeatableFormSection,
51111
51153
  "ReplyTree": ReplyTree,
51112
- "RichBlockEditor": RichBlockEditor,
51154
+ "RichTextEditor": RichTextEditor,
51113
51155
  "RuntimeDebugger": RuntimeDebugger,
51114
51156
  "ScaledDiagram": ScaledDiagram,
51115
51157
  "ScoreDisplay": ScoreDisplay,
@@ -51263,10 +51305,10 @@ function enrichFormFields(fields, entityDef) {
51263
51305
  }
51264
51306
  if (field && typeof field === "object" && !Array.isArray(field) && !React94__namespace.default.isValidElement(field) && !(field instanceof Date)) {
51265
51307
  const obj = field;
51266
- const fieldName = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
51267
- if (!fieldName) return field;
51308
+ const fieldName2 = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
51309
+ if (!fieldName2) return field;
51268
51310
  if (obj.type || obj.inputType) return field;
51269
- const entityField = fieldMap.get(fieldName);
51311
+ const entityField = fieldMap.get(fieldName2);
51270
51312
  if (!entityField) return field;
51271
51313
  const enriched = { ...obj, type: entityField.type };
51272
51314
  if (entityField.required && !("required" in obj)) {
@@ -51316,9 +51358,9 @@ function enrichDetailFields(fields, entityDef) {
51316
51358
  }
51317
51359
  if (field && typeof field === "object" && !Array.isArray(field) && !React94__namespace.default.isValidElement(field) && !(field instanceof Date)) {
51318
51360
  const obj = field;
51319
- const fieldName = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
51320
- if (!fieldName || obj.type) return field;
51321
- const meta = metaFor(fieldName);
51361
+ const fieldName2 = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
51362
+ if (!fieldName2 || obj.type) return field;
51363
+ const meta = metaFor(fieldName2);
51322
51364
  return meta ? { ...obj, ...meta } : field;
51323
51365
  }
51324
51366
  return field;
@@ -52157,9 +52199,9 @@ function UISlotRenderer({
52157
52199
  "ui-slot-renderer relative min-h-full",
52158
52200
  className
52159
52201
  ), children: [
52160
- /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex min-h-full", children: [
52161
- /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 shrink-0" }),
52162
- /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-h-[200px]" })
52202
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex min-h-full flex-col lg:flex-row", children: [
52203
+ /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 lg:shrink-0" }),
52204
+ /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-w-0 min-h-[200px]" })
52163
52205
  ] }),
52164
52206
  /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "modal", portal: true }),
52165
52207
  /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "drawer", portal: true }),
@@ -56056,7 +56098,7 @@ function buildTraitRenderConfig(binding, traitConfigsByName, payload) {
56056
56098
  }
56057
56099
  return void 0;
56058
56100
  }
56059
- function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
56101
+ function createSharedEntityWriter(binding, tick, traitStatesRef, emit, traitConfigsByName) {
56060
56102
  return (scratch) => {
56061
56103
  const traitName = binding.trait.name;
56062
56104
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -56065,9 +56107,13 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
56065
56107
  const writes = [];
56066
56108
  const ctx = evaluator.createMinimalContext(scratchEntity, {}, currentState);
56067
56109
  const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
56068
- const callSiteConfig = getBindingConfig(binding);
56069
- if (declaredDefaults || callSiteConfig) {
56070
- ctx.config = { ...declaredDefaults ?? {}, ...callSiteConfig ?? {} };
56110
+ const resolvedByName = traitConfigsByName?.[traitName];
56111
+ const callSiteRaw = getBindingConfig(binding);
56112
+ const callSiteConfig = callSiteRaw ? Object.fromEntries(
56113
+ Object.entries(callSiteRaw).filter(([, v]) => !containsConfigForward(v))
56114
+ ) : void 0;
56115
+ if (declaredDefaults || resolvedByName || callSiteConfig) {
56116
+ ctx.config = { ...declaredDefaults ?? {}, ...resolvedByName ?? {}, ...callSiteConfig ?? {} };
56071
56117
  }
56072
56118
  ctx.mutateEntity = (changes) => {
56073
56119
  for (const [field, value] of Object.entries(changes)) {
@@ -56670,7 +56716,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
56670
56716
  const interval = entries[0].tick.interval;
56671
56717
  const onDue = timedTick(`tick:shared:${group.storeKey}@${String(interval)}`, () => {
56672
56718
  const writers = entries.map(
56673
- ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
56719
+ ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter, traitConfigsByName)
56674
56720
  );
56675
56721
  runTickFrame(group.storeKey, writers, sharedEntityStore);
56676
56722
  });
@@ -59183,11 +59229,11 @@ function OrbInspector({ node, schema, editable = false, userType = "builder", th
59183
59229
  const handleAddField = React94.useCallback(() => {
59184
59230
  eventBus.emit("UI:ADD_FIELD", {});
59185
59231
  }, [eventBus]);
59186
- const handleUpdateField = React94.useCallback((fieldName, updates) => {
59187
- eventBus.emit("UI:UPDATE_FIELD", { fieldName, updates });
59232
+ const handleUpdateField = React94.useCallback((fieldName2, updates) => {
59233
+ eventBus.emit("UI:UPDATE_FIELD", { fieldName: fieldName2, updates });
59188
59234
  }, [eventBus]);
59189
- const handleRemoveField = React94.useCallback((fieldName) => {
59190
- eventBus.emit("UI:REMOVE_FIELD", { fieldName });
59235
+ const handleRemoveField = React94.useCallback((fieldName2) => {
59236
+ eventBus.emit("UI:REMOVE_FIELD", { fieldName: fieldName2 });
59191
59237
  }, [eventBus]);
59192
59238
  const handleGuardChange = React94.useCallback((guardExpr) => {
59193
59239
  if (!editable) return;