@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.
@@ -24025,8 +24025,9 @@ function DataGrid({
24025
24025
  const titleField = fieldDefs.find((f3) => f3.variant === "h3" || f3.variant === "h4") ?? fieldDefs[0];
24026
24026
  const badgeFields = fieldDefs.filter((f3) => f3.variant === "badge" && f3 !== titleField);
24027
24027
  const bodyFields = fieldDefs.filter((f3) => f3 !== titleField && !badgeFields.includes(f3));
24028
- const primaryActions = actionDefs.filter((a) => a.variant !== "danger");
24029
- const dangerActions = actionDefs.filter((a) => a.variant === "danger");
24028
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
24029
+ const inlineCardActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
24030
+ const menuCardActions = actionDefs.filter((a) => !inlineCardActions.includes(a));
24030
24031
  const fireAction = (action, itemData) => {
24031
24032
  if (action.navigatesTo) {
24032
24033
  const url = action.navigatesTo.replace(
@@ -24046,6 +24047,9 @@ function DataGrid({
24046
24047
  e.stopPropagation();
24047
24048
  fireAction(action, itemData);
24048
24049
  };
24050
+ const cardClickAction = actionDefs.find((a) => a.variant !== "danger");
24051
+ const handleCardClick = cardClickAction ? (itemData) => () => fireAction(cardClickAction, itemData) : void 0;
24052
+ const stopCardClick = handleCardClick ? (e) => e.stopPropagation() : void 0;
24049
24053
  const hasRenderProp = typeof children === "function";
24050
24054
  React77.useEffect(() => {
24051
24055
  if (data.length > 0 && !hasRenderProp && fieldDefs.length === 0) {
@@ -24117,55 +24121,40 @@ function DataGrid({
24117
24121
  {
24118
24122
  "data-entity-row": true,
24119
24123
  "data-entity-id": id,
24120
- className: cn("relative group/rowactions", isSelected && "ring-2 ring-primary rounded-lg"),
24124
+ onClick: handleCardClick?.(itemData),
24125
+ className: cn("relative group/rowactions", handleCardClick && "cursor-pointer", isSelected && "ring-2 ring-primary rounded-lg"),
24121
24126
  children: [
24122
24127
  children(itemData, index),
24123
- actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(exports.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: [
24124
- /* @__PURE__ */ jsxRuntime.jsxs(exports.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: [
24125
- (maxInlineActions != null ? actionDefs.slice(0, maxInlineActions) : actionDefs).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
24126
- exports.Button,
24127
- {
24128
- variant: action.variant === "primary" ? "primary" : "ghost",
24129
- size: "sm",
24130
- onClick: handleActionClick(action, itemData),
24131
- "data-testid": `action-${action.event}`,
24132
- "data-row-id": String(itemData.id),
24133
- className: action.variant === "danger" ? "text-error hover:text-error hover:bg-error/10" : void 0,
24134
- children: [
24135
- action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
24136
- action.label
24137
- ]
24138
- },
24139
- idx
24140
- )),
24141
- maxInlineActions != null && actionDefs.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
24142
- exports.Menu,
24143
- {
24144
- position: "bottom-end",
24145
- trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
24146
- items: actionDefs.slice(maxInlineActions).map((action) => ({
24147
- label: action.label,
24148
- icon: action.icon,
24149
- event: action.event,
24150
- onClick: () => fireAction(action, itemData)
24151
- }))
24152
- }
24153
- )
24154
- ] }),
24155
- /* @__PURE__ */ jsxRuntime.jsx(exports.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(
24128
+ actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.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(exports.HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: [
24129
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
24130
+ exports.Button,
24131
+ {
24132
+ variant: "primary",
24133
+ size: "sm",
24134
+ onClick: handleActionClick(action, itemData),
24135
+ "data-testid": `action-${action.event}`,
24136
+ "data-row-id": String(itemData.id),
24137
+ children: [
24138
+ action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
24139
+ action.label
24140
+ ]
24141
+ },
24142
+ idx
24143
+ )),
24144
+ menuCardActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
24156
24145
  exports.Menu,
24157
24146
  {
24158
24147
  position: "bottom-end",
24159
24148
  trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
24160
- items: actionDefs.map((action) => ({
24149
+ items: menuCardActions.map((action) => ({
24161
24150
  label: action.label,
24162
24151
  icon: action.icon,
24163
- event: action.event,
24152
+ variant: action.variant === "danger" ? "danger" : "default",
24164
24153
  onClick: () => fireAction(action, itemData)
24165
24154
  }))
24166
24155
  }
24167
- ) })
24168
- ] })
24156
+ )
24157
+ ] }) })
24169
24158
  ]
24170
24159
  },
24171
24160
  id
@@ -24179,12 +24168,14 @@ function DataGrid({
24179
24168
  {
24180
24169
  "data-entity-row": true,
24181
24170
  "data-entity-id": id,
24171
+ onClick: handleCardClick?.(itemData),
24182
24172
  className: cn(
24183
24173
  "bg-card rounded-lg",
24184
24174
  "border border-border",
24185
24175
  "shadow-elevation-card hover:shadow-elevation-dialog",
24186
24176
  "hover:border-primary transition-all",
24187
24177
  "flex flex-col",
24178
+ handleCardClick && "cursor-pointer",
24188
24179
  isSelected && "ring-2 ring-primary border-primary"
24189
24180
  ),
24190
24181
  children: [
@@ -24234,53 +24225,35 @@ function DataGrid({
24234
24225
  ] }, field.name);
24235
24226
  }) })
24236
24227
  ] }),
24237
- (primaryActions.length > 0 || dangerActions.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", className: "flex-shrink-0", children: [
24238
- (maxInlineActions != null ? primaryActions.slice(0, maxInlineActions) : primaryActions).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
24228
+ actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", onClick: stopCardClick, className: "flex-shrink-0", children: [
24229
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
24239
24230
  exports.Button,
24240
24231
  {
24241
- variant: action.variant === "primary" ? "primary" : "ghost",
24232
+ variant: "primary",
24242
24233
  size: "sm",
24243
24234
  onClick: handleActionClick(action, itemData),
24244
24235
  "data-testid": `action-${action.event}`,
24245
24236
  "data-row-id": String(itemData.id),
24246
24237
  "aria-label": action.label,
24247
24238
  title: action.label,
24248
- className: cn(
24249
- action.variant === "primary" ? void 0 : "text-muted-foreground hover:text-foreground",
24250
- action.icon && "px-2"
24251
- ),
24239
+ className: cn(action.icon && "px-2"),
24252
24240
  children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
24253
24241
  },
24254
24242
  idx
24255
24243
  )),
24256
- maxInlineActions != null && primaryActions.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
24244
+ menuCardActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
24257
24245
  exports.Menu,
24258
24246
  {
24259
24247
  position: "bottom-end",
24260
24248
  trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
24261
- items: primaryActions.slice(maxInlineActions).map((action) => ({
24249
+ items: menuCardActions.map((action) => ({
24262
24250
  label: action.label,
24263
24251
  icon: action.icon,
24264
- event: action.event,
24252
+ variant: action.variant === "danger" ? "danger" : "default",
24265
24253
  onClick: () => fireAction(action, itemData)
24266
24254
  }))
24267
24255
  }
24268
- ),
24269
- dangerActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
24270
- exports.Button,
24271
- {
24272
- variant: "ghost",
24273
- size: "sm",
24274
- onClick: handleActionClick(action, itemData),
24275
- "data-testid": `action-${action.event}`,
24276
- "data-row-id": String(itemData.id),
24277
- "aria-label": action.label,
24278
- title: action.label,
24279
- className: "text-error hover:text-error hover:bg-error/10 px-2",
24280
- children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
24281
- },
24282
- `danger-${idx}`
24283
- ))
24256
+ )
24284
24257
  ] })
24285
24258
  ] }) }),
24286
24259
  bodyFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "px-4 pt-2 pb-4 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", children: [
@@ -24301,8 +24274,8 @@ function DataGrid({
24301
24274
  }
24302
24275
  return /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", className: "items-center", children: [
24303
24276
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
24304
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
24305
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", children: formatValue(value, field.format) })
24277
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
24278
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
24306
24279
  ] }, field.name);
24307
24280
  }) })
24308
24281
  ] }) })
@@ -24522,54 +24495,59 @@ function DataList({
24522
24495
  if (!itemActions || itemActions.length === 0) return null;
24523
24496
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
24524
24497
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
24525
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", className: "flex-shrink-0", children: [
24526
- inline.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
24527
- exports.Button,
24528
- {
24529
- variant: action.variant ?? "ghost",
24530
- size: "sm",
24531
- onClick: handleActionClick(action, itemData),
24532
- "data-testid": `action-${action.event}`,
24533
- "data-row-id": String(itemData.id),
24534
- className: cn(
24535
- action.variant === "danger" && "text-error hover:bg-error/10",
24536
- // Must sit on the Button itself: the variant's own text colour
24537
- // beats an inherited one from the row wrapper.
24538
- onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
24539
- ),
24540
- children: [
24541
- action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
24542
- action.label
24543
- ]
24544
- },
24545
- idx
24546
- )),
24547
- overflow.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
24548
- exports.Menu,
24549
- {
24550
- position: "bottom-end",
24551
- trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
24552
- items: overflow.map((action) => ({
24553
- label: action.label,
24554
- icon: action.icon,
24555
- event: action.event,
24556
- variant: action.variant === "danger" ? "danger" : "default",
24557
- onClick: () => eventBus.emit(`UI:${action.event}`, {
24558
- id: itemData.id,
24559
- row: itemData
24560
- })
24561
- }))
24562
- }
24563
- )
24564
- ] });
24498
+ return (
24499
+ // stopPropagation at the cluster: the "⋯" trigger opens the overflow
24500
+ // menu without also firing the row's default click (inline buttons
24501
+ // already stop it in handleActionClick; the Menu panel is portaled).
24502
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, className: "flex-shrink-0", children: [
24503
+ inline.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
24504
+ exports.Button,
24505
+ {
24506
+ variant: action.variant ?? "ghost",
24507
+ size: "sm",
24508
+ onClick: handleActionClick(action, itemData),
24509
+ "data-testid": `action-${action.event}`,
24510
+ "data-row-id": String(itemData.id),
24511
+ className: cn(
24512
+ action.variant === "danger" && "text-error hover:bg-error/10",
24513
+ // Must sit on the Button itself: the variant's own text colour
24514
+ // beats an inherited one from the row wrapper.
24515
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
24516
+ ),
24517
+ children: [
24518
+ action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
24519
+ action.label
24520
+ ]
24521
+ },
24522
+ idx
24523
+ )),
24524
+ overflow.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
24525
+ exports.Menu,
24526
+ {
24527
+ position: "bottom-end",
24528
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
24529
+ items: overflow.map((action) => ({
24530
+ label: action.label,
24531
+ icon: action.icon,
24532
+ variant: action.variant === "danger" ? "danger" : "default",
24533
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
24534
+ id: itemData.id,
24535
+ row: itemData
24536
+ })
24537
+ }))
24538
+ }
24539
+ )
24540
+ ] })
24541
+ );
24565
24542
  };
24543
+ const rowClickEvent = itemClickEvent || itemActions?.find((a) => a.variant !== "danger")?.event;
24566
24544
  const handleRowClick = (itemData) => () => {
24567
- if (!itemClickEvent) return;
24545
+ if (!rowClickEvent) return;
24568
24546
  const payload = {
24569
24547
  id: itemData.id,
24570
24548
  row: itemData
24571
24549
  };
24572
- eventBus.emit(`UI:${itemClickEvent}`, payload);
24550
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
24573
24551
  };
24574
24552
  if (isLoading) {
24575
24553
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
@@ -24617,10 +24595,10 @@ function DataList({
24617
24595
  {
24618
24596
  "data-entity-row": true,
24619
24597
  "data-entity-id": id,
24620
- onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
24598
+ onClick: rowClickEvent ? handleRowClick(itemData) : void 0,
24621
24599
  className: cn(
24622
24600
  "flex px-4 group/rowactions",
24623
- itemClickEvent && "cursor-pointer",
24601
+ rowClickEvent && "cursor-pointer",
24624
24602
  isSent ? "justify-end" : "justify-start"
24625
24603
  ),
24626
24604
  children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -24696,7 +24674,7 @@ function DataList({
24696
24674
  const id2 = itemData.id || String(index);
24697
24675
  const actions = renderItemActions(itemData);
24698
24676
  return wrapDnd(
24699
- /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { "data-entity-row": true, "data-entity-id": id2, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", itemClickEvent && "cursor-pointer"), children: [
24677
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { "data-entity-row": true, "data-entity-id": id2, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", rowClickEvent && "cursor-pointer"), children: [
24700
24678
  children(itemData, index),
24701
24679
  actions && /* @__PURE__ */ jsxRuntime.jsxs(exports.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: [
24702
24680
  /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: actions }),
@@ -24708,7 +24686,6 @@ function DataList({
24708
24686
  items: (itemActions ?? []).map((action) => ({
24709
24687
  label: action.label,
24710
24688
  icon: action.icon,
24711
- event: action.event,
24712
24689
  variant: action.variant === "danger" ? "danger" : "default",
24713
24690
  onClick: () => eventBus.emit(`UI:${action.event}`, {
24714
24691
  id: itemData.id,
@@ -24725,12 +24702,16 @@ function DataList({
24725
24702
  const id = itemData.id || String(index);
24726
24703
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
24727
24704
  return wrapDnd(
24728
- /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { "data-entity-row": true, "data-entity-id": id, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn(itemClickEvent && "cursor-pointer"), children: [
24705
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
24729
24706
  /* @__PURE__ */ jsxRuntime.jsxs(
24730
24707
  exports.Box,
24731
24708
  {
24732
24709
  className: cn(
24733
- "group flex items-center gap-4 transition-all duration-fast",
24710
+ // items-start, not items-center: a multi-line row (title + meta +
24711
+ // progress) centred its action cluster in the vertical middle, so
24712
+ // the buttons floated between the meta fields instead of anchoring
24713
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
24714
+ "group flex items-start gap-4 transition-all duration-fast",
24734
24715
  isCompact ? "px-4 py-2" : "px-6 py-4",
24735
24716
  "hover:bg-muted/80",
24736
24717
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -24761,11 +24742,19 @@ function DataList({
24761
24742
  if (value === void 0 || value === null || value === "") return null;
24762
24743
  return /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", className: "items-center", children: [
24763
24744
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
24764
- /* @__PURE__ */ jsxRuntime.jsxs(exports.Typography, { variant: "caption", color: "secondary", children: [
24765
- field.label ?? fieldLabel3(field.name),
24766
- ":"
24767
- ] }),
24768
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
24745
+ /* @__PURE__ */ jsxRuntime.jsxs(
24746
+ exports.Typography,
24747
+ {
24748
+ variant: "caption",
24749
+ color: "secondary",
24750
+ className: cn(field.format !== "boolean" && "sr-only"),
24751
+ children: [
24752
+ field.label ?? fieldLabel3(field.name),
24753
+ ":"
24754
+ ]
24755
+ }
24756
+ ),
24757
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
24769
24758
  ] }, field.name);
24770
24759
  }) }),
24771
24760
  progressFields.map((field) => {
@@ -24784,7 +24773,7 @@ function DataList({
24784
24773
  ]
24785
24774
  }
24786
24775
  ),
24787
- isCard && !isLast && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "mx-6 border-b border-border/40" })
24776
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
24788
24777
  ] }, id)
24789
24778
  );
24790
24779
  };
@@ -24794,7 +24783,13 @@ function DataList({
24794
24783
  {
24795
24784
  className: cn(
24796
24785
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
24797
- !isCard && gapClass,
24786
+ // `gap-*` is inert on a block container, and Box only emits a display
24787
+ // class when its `display` prop is set — so every non-card list had
24788
+ // been asking for a gap that CSS silently dropped. flex-col makes it
24789
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
24790
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
24791
+ !isCard && "flex flex-col",
24792
+ !isCard && !isCompact && gapClass,
24798
24793
  listLookStyles[look],
24799
24794
  className
24800
24795
  ),
@@ -33334,7 +33329,7 @@ var init_MapView = __esm({
33334
33329
  shadowSize: [41, 41]
33335
33330
  });
33336
33331
  L.Marker.prototype.options.icon = defaultIcon;
33337
- const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback108, useState: useState104 } = React77__namespace.default;
33332
+ const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback108, useState: useState106 } = React77__namespace.default;
33338
33333
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
33339
33334
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
33340
33335
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -33379,7 +33374,7 @@ var init_MapView = __esm({
33379
33374
  showAttribution = true
33380
33375
  }) {
33381
33376
  const eventBus = useEventBus2();
33382
- const [clickedPosition, setClickedPosition] = useState104(null);
33377
+ const [clickedPosition, setClickedPosition] = useState106(null);
33383
33378
  const handleMapClick = useCallback108((lat, lng) => {
33384
33379
  if (showClickedPin) {
33385
33380
  setClickedPosition({ lat, lng });
@@ -37741,741 +37736,775 @@ var init_PositionedCanvas = __esm({
37741
37736
  exports.PositionedCanvas.displayName = "PositionedCanvas";
37742
37737
  }
37743
37738
  });
37744
- function nextBlockId(prefix = "blk") {
37745
- _idSeq += 1;
37746
- const random = Math.random().toString(36).slice(2, 8);
37747
- return `${prefix}-${Date.now().toString(36)}-${_idSeq}-${random}`;
37739
+ function safeUrl(raw, kinds) {
37740
+ const url = raw.trim();
37741
+ if (url.startsWith("#") || url.startsWith("/")) return url;
37742
+ const lower = url.toLowerCase();
37743
+ for (const kind of kinds) {
37744
+ if (lower.startsWith(kind)) return url;
37745
+ }
37746
+ return null;
37748
37747
  }
37749
- function normalizeBlocks(raw) {
37750
- if (!Array.isArray(raw) || raw.length === 0) return [createBlock("paragraph")];
37751
- return raw.map((row) => {
37752
- const entity = row;
37753
- const rawType = entity.type;
37754
- const type = typeof rawType === "string" && BLOCK_TYPES.has(rawType) ? rawType : "paragraph";
37755
- const id = typeof entity.id === "string" && entity.id ? entity.id : nextBlockId(type);
37756
- return { ...entity, id, type };
37757
- });
37748
+ function sanitizeNode(el) {
37749
+ const children = Array.from(el.children);
37750
+ for (const child of children) {
37751
+ const tag = child.tagName.toLowerCase();
37752
+ if (DROP_TAGS.has(tag)) {
37753
+ child.remove();
37754
+ continue;
37755
+ }
37756
+ const allowed = ALLOWED_ATTRS[tag];
37757
+ if (!allowed) {
37758
+ sanitizeNode(child);
37759
+ child.replaceWith(...Array.from(child.childNodes));
37760
+ continue;
37761
+ }
37762
+ for (const attr of Array.from(child.attributes)) {
37763
+ const name = attr.name.toLowerCase();
37764
+ if (!allowed.includes(name)) {
37765
+ child.removeAttribute(attr.name);
37766
+ continue;
37767
+ }
37768
+ if (name === "href") {
37769
+ const url = safeUrl(attr.value, ["http://", "https://", "mailto:"]);
37770
+ if (url === null) child.removeAttribute(attr.name);
37771
+ else child.setAttribute("href", url);
37772
+ }
37773
+ if (name === "src") {
37774
+ const url = safeUrl(attr.value, ["http://", "https://", "data:image/"]);
37775
+ if (url === null) child.remove();
37776
+ else child.setAttribute("src", url);
37777
+ }
37778
+ }
37779
+ sanitizeNode(child);
37780
+ }
37758
37781
  }
37759
- function createBlock(type) {
37760
- switch (type) {
37761
- case "bullet-list":
37762
- case "numbered-list":
37763
- return {
37764
- id: nextBlockId(type),
37765
- type,
37766
- children: [
37767
- { id: nextBlockId("li"), type: "paragraph", content: "" }
37768
- ]
37769
- };
37770
- case "image":
37771
- return {
37772
- id: nextBlockId(type),
37773
- type,
37774
- content: "",
37775
- metadata: { url: "", caption: "" }
37776
- };
37777
- case "code":
37778
- return {
37779
- id: nextBlockId(type),
37780
- type,
37781
- content: "",
37782
- metadata: { language: "plaintext" }
37783
- };
37784
- case "divider":
37785
- return { id: nextBlockId(type), type };
37786
- default:
37787
- return { id: nextBlockId(type), type, content: "" };
37782
+ function sanitizeRichHtml(html) {
37783
+ if (!html || typeof html !== "string") return "";
37784
+ if (typeof window === "undefined" || typeof DOMParser === "undefined") return "";
37785
+ const doc = new DOMParser().parseFromString(html, "text/html");
37786
+ sanitizeNode(doc.body);
37787
+ if (!doc.body.querySelector("img, hr") && (doc.body.textContent ?? "").trim().length === 0) {
37788
+ return "";
37788
37789
  }
37790
+ return doc.body.innerHTML.replace(/​/g, "");
37791
+ }
37792
+ function htmlIsEmpty(el) {
37793
+ if (el.querySelector("img, hr")) return false;
37794
+ return (el.textContent ?? "").trim().length === 0;
37789
37795
  }
37790
- function replaceBlock(blocks, id, updater) {
37791
- return blocks.map((block) => block.id === id ? updater(block) : block);
37796
+ function closestBlock(node, root) {
37797
+ let cur = node;
37798
+ while (cur && cur !== root) {
37799
+ if (cur instanceof Element && BLOCK_TAGS.has(cur.tagName)) return cur;
37800
+ cur = cur.parentNode;
37801
+ }
37802
+ return null;
37792
37803
  }
37793
- function removeBlock(blocks, id) {
37794
- return blocks.filter((block) => block.id !== id);
37804
+ function RichTextStyles() {
37805
+ return /* @__PURE__ */ jsxRuntime.jsx("style", { children: RICH_TEXT_CSS });
37795
37806
  }
37796
- function duplicateBlock(block) {
37807
+ function readToolbarState(root) {
37808
+ const sel = window.getSelection();
37809
+ if (!sel || sel.rangeCount === 0) return IDLE_TOOLBAR;
37810
+ const anchor = sel.anchorNode;
37811
+ if (!anchor || !root.contains(anchor)) return IDLE_TOOLBAR;
37812
+ let block = null;
37813
+ let link = false;
37814
+ let node = anchor;
37815
+ while (node && node !== root) {
37816
+ if (node instanceof Element) {
37817
+ const tag = node.tagName.toLowerCase();
37818
+ if (!block && (tag === "h1" || tag === "h2" || tag === "h3" || tag === "blockquote" || tag === "pre")) {
37819
+ block = tag;
37820
+ }
37821
+ if (tag === "a") link = true;
37822
+ }
37823
+ node = node.parentNode;
37824
+ }
37797
37825
  return {
37798
- ...block,
37799
- id: nextBlockId(block.type),
37800
- children: block.children?.map((child) => ({
37801
- ...child,
37802
- id: nextBlockId("li")
37803
- })),
37804
- metadata: block.metadata ? { ...block.metadata } : void 0
37826
+ bold: document.queryCommandState("bold"),
37827
+ italic: document.queryCommandState("italic"),
37828
+ underline: document.queryCommandState("underline"),
37829
+ strikeThrough: document.queryCommandState("strikeThrough"),
37830
+ block,
37831
+ bullets: document.queryCommandState("insertUnorderedList"),
37832
+ numbers: document.queryCommandState("insertOrderedList"),
37833
+ link
37805
37834
  };
37806
37835
  }
37807
- function insertAfter(blocks, targetId, inserted) {
37808
- const idx = blocks.findIndex((b) => b.id === targetId);
37809
- if (idx === -1) return [...blocks, inserted];
37810
- const next = blocks.slice();
37811
- next.splice(idx + 1, 0, inserted);
37812
- return next;
37813
- }
37814
- function changeBlockType(block, type) {
37815
- if (block.type === type) return block;
37816
- if (type === "bullet-list" || type === "numbered-list") {
37817
- if (block.children && block.children.length > 0) {
37818
- return { ...block, type };
37836
+ function ToolbarButton({ icon: IconCmp, label, active, onExec }) {
37837
+ return /* @__PURE__ */ jsxRuntime.jsx(
37838
+ exports.Button,
37839
+ {
37840
+ type: "button",
37841
+ variant: "ghost",
37842
+ size: "sm",
37843
+ "aria-label": label,
37844
+ "aria-pressed": active,
37845
+ title: label,
37846
+ className: cn(
37847
+ "h-8 w-8 p-0 gap-0 justify-center",
37848
+ active && "bg-muted text-foreground"
37849
+ ),
37850
+ onMouseDown: (e) => {
37851
+ e.preventDefault();
37852
+ onExec();
37853
+ },
37854
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconCmp, { size: 15 })
37819
37855
  }
37820
- const seed2 = block.content ?? "";
37821
- return {
37822
- id: block.id,
37823
- type,
37824
- children: [
37825
- { id: nextBlockId("li"), type: "paragraph", content: seed2 }
37826
- ]
37827
- };
37828
- }
37829
- if (type === "divider") {
37830
- return { id: block.id, type };
37831
- }
37832
- if (type === "image") {
37833
- return {
37834
- id: block.id,
37835
- type,
37836
- content: "",
37837
- metadata: { url: "", caption: block.content ?? "" }
37838
- };
37839
- }
37840
- if (type === "code") {
37841
- return {
37842
- id: block.id,
37843
- type,
37844
- content: block.content ?? "",
37845
- metadata: { language: "plaintext" }
37856
+ );
37857
+ }
37858
+ var CHANGE_DEBOUNCE_MS, ALLOWED_ATTRS, DROP_TAGS, MD_BLOCK_PREFIXES, BLOCK_TAGS, MD_INLINE_RULES, RICH_TEXT_CSS, IDLE_TOOLBAR; exports.RichTextEditor = void 0;
37859
+ var init_RichTextEditor = __esm({
37860
+ "components/core/molecules/RichTextEditor.tsx"() {
37861
+ "use client";
37862
+ init_cn();
37863
+ init_Box();
37864
+ init_Button();
37865
+ init_useEventBus();
37866
+ CHANGE_DEBOUNCE_MS = 400;
37867
+ ALLOWED_ATTRS = {
37868
+ p: [],
37869
+ h1: [],
37870
+ h2: [],
37871
+ h3: [],
37872
+ h4: [],
37873
+ ul: [],
37874
+ ol: [],
37875
+ li: [],
37876
+ blockquote: [],
37877
+ pre: [],
37878
+ code: [],
37879
+ b: [],
37880
+ strong: [],
37881
+ i: [],
37882
+ em: [],
37883
+ u: [],
37884
+ s: [],
37885
+ strike: [],
37886
+ br: [],
37887
+ hr: [],
37888
+ div: [],
37889
+ span: [],
37890
+ a: ["href"],
37891
+ img: ["src", "alt"]
37892
+ };
37893
+ DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "link", "meta", "form", "input", "button", "textarea", "select"]);
37894
+ MD_BLOCK_PREFIXES = {
37895
+ "#": { kind: "block", tag: "h1" },
37896
+ "##": { kind: "block", tag: "h2" },
37897
+ "###": { kind: "block", tag: "h3" },
37898
+ ">": { kind: "block", tag: "blockquote" },
37899
+ "```": { kind: "block", tag: "pre" },
37900
+ "-": { kind: "list", command: "insertUnorderedList" },
37901
+ "*": { kind: "list", command: "insertUnorderedList" },
37902
+ "1.": { kind: "list", command: "insertOrderedList" }
37903
+ };
37904
+ BLOCK_TAGS = /* @__PURE__ */ new Set(["P", "DIV", "LI", "H1", "H2", "H3", "H4", "BLOCKQUOTE", "PRE"]);
37905
+ MD_INLINE_RULES = [
37906
+ { pattern: /\*\*([^*\n]+)\*\*$/, wrap: "strong" },
37907
+ { pattern: /(^|[^*])\*([^*\n]+)\*$/, wrap: "em" },
37908
+ { pattern: /`([^`\n]+)`$/, wrap: "code" }
37909
+ ];
37910
+ RICH_TEXT_CSS = `
37911
+ .almadar-rich-text { line-height: 1.7; color: var(--color-foreground); }
37912
+ .almadar-rich-text h1 { font-size: 1.875rem; font-weight: 700; line-height: 1.25; margin: 1.25em 0 0.4em; }
37913
+ .almadar-rich-text h2 { font-size: 1.5rem; font-weight: 650; line-height: 1.3; margin: 1.1em 0 0.4em; }
37914
+ .almadar-rich-text h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.35; margin: 1em 0 0.35em; }
37915
+ .almadar-rich-text h1:first-child, .almadar-rich-text h2:first-child, .almadar-rich-text h3:first-child { margin-top: 0; }
37916
+ .almadar-rich-text p { margin: 0.5em 0; }
37917
+ .almadar-rich-text ul { list-style: disc; padding-inline-start: 1.5rem; margin: 0.5em 0; }
37918
+ .almadar-rich-text ol { list-style: decimal; padding-inline-start: 1.5rem; margin: 0.5em 0; }
37919
+ .almadar-rich-text li { margin: 0.25em 0; }
37920
+ .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; }
37921
+ .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; }
37922
+ .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; }
37923
+ .almadar-rich-text pre code { background: transparent; padding: 0; }
37924
+ .almadar-rich-text a { color: var(--color-primary); text-decoration: underline; }
37925
+ .almadar-rich-text hr { border: none; border-top: 1px solid var(--color-border); margin: 1.25em 0; }
37926
+ .almadar-rich-text img { max-width: 100%; border-radius: 0.375rem; }
37927
+ `;
37928
+ IDLE_TOOLBAR = {
37929
+ bold: false,
37930
+ italic: false,
37931
+ underline: false,
37932
+ strikeThrough: false,
37933
+ block: null,
37934
+ bullets: false,
37935
+ numbers: false,
37936
+ link: false
37937
+ };
37938
+ exports.RichTextEditor = ({
37939
+ value,
37940
+ onChange,
37941
+ changeEvent,
37942
+ readOnly = false,
37943
+ placeholder,
37944
+ showToolbar = true,
37945
+ className
37946
+ }) => {
37947
+ const { t } = hooks.useTranslate();
37948
+ const ref = React77.useRef(null);
37949
+ const [toolbar, setToolbar] = React77.useState(IDLE_TOOLBAR);
37950
+ const [empty, setEmpty] = React77.useState(() => !value || !sanitizeRichHtml(value).trim());
37951
+ const eventBus = useEventBus();
37952
+ const onChangeRef = React77.useRef(onChange);
37953
+ const changeEventRef = React77.useRef(changeEvent);
37954
+ React77.useEffect(() => {
37955
+ onChangeRef.current = onChange;
37956
+ changeEventRef.current = changeEvent;
37957
+ }, [onChange, changeEvent]);
37958
+ const emitTimerRef = React77.useRef(null);
37959
+ const emitNow = React77.useCallback(() => {
37960
+ const el = ref.current;
37961
+ if (!el) return;
37962
+ const html = sanitizeRichHtml(el.innerHTML);
37963
+ onChangeRef.current?.(html);
37964
+ const evt = changeEventRef.current;
37965
+ if (evt) eventBus.emit(`UI:${evt}`, { value: html });
37966
+ }, [eventBus]);
37967
+ const scheduleEmit = React77.useCallback(() => {
37968
+ if (emitTimerRef.current) clearTimeout(emitTimerRef.current);
37969
+ emitTimerRef.current = setTimeout(() => {
37970
+ emitTimerRef.current = null;
37971
+ emitNow();
37972
+ }, CHANGE_DEBOUNCE_MS);
37973
+ }, [emitNow]);
37974
+ const flushEmit = React77.useCallback(() => {
37975
+ if (!emitTimerRef.current) return;
37976
+ clearTimeout(emitTimerRef.current);
37977
+ emitTimerRef.current = null;
37978
+ emitNow();
37979
+ }, [emitNow]);
37980
+ React77.useEffect(() => () => {
37981
+ if (emitTimerRef.current) {
37982
+ clearTimeout(emitTimerRef.current);
37983
+ emitTimerRef.current = null;
37984
+ emitNow();
37985
+ }
37986
+ }, [emitNow]);
37987
+ React77.useEffect(() => {
37988
+ if (readOnly) return;
37989
+ const el = ref.current;
37990
+ if (!el) return;
37991
+ if (document.activeElement === el) return;
37992
+ const next = sanitizeRichHtml(value ?? "") || "<p><br></p>";
37993
+ if (el.innerHTML !== next) {
37994
+ el.innerHTML = next;
37995
+ setEmpty(htmlIsEmpty(el));
37996
+ }
37997
+ }, [readOnly, value]);
37998
+ React77.useEffect(() => {
37999
+ if (readOnly) return;
38000
+ document.execCommand("defaultParagraphSeparator", false, "p");
38001
+ }, [readOnly]);
38002
+ React77.useEffect(() => {
38003
+ if (readOnly || !showToolbar) return;
38004
+ const onSelectionChange = () => {
38005
+ const el = ref.current;
38006
+ if (!el) return;
38007
+ setToolbar(readToolbarState(el));
38008
+ };
38009
+ document.addEventListener("selectionchange", onSelectionChange);
38010
+ return () => document.removeEventListener("selectionchange", onSelectionChange);
38011
+ }, [readOnly, showToolbar]);
38012
+ const tryInlineMarkdown = React77.useCallback(() => {
38013
+ const root = ref.current;
38014
+ if (!root) return false;
38015
+ const sel = window.getSelection();
38016
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
38017
+ const node = sel.anchorNode;
38018
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
38019
+ if (node.parentElement?.closest("code, pre")) return false;
38020
+ const text = (node.textContent ?? "").slice(0, sel.anchorOffset);
38021
+ for (const rule of MD_INLINE_RULES) {
38022
+ const m = rule.pattern.exec(text);
38023
+ if (!m) continue;
38024
+ const inner = rule.wrap === "em" ? m[2] : m[1];
38025
+ if (!inner || !inner.trim()) continue;
38026
+ const lead = rule.wrap === "em" ? m[1]?.length ?? 0 : 0;
38027
+ const range = document.createRange();
38028
+ range.setStart(node, m.index + lead);
38029
+ range.setEnd(node, sel.anchorOffset);
38030
+ const el = document.createElement(rule.wrap);
38031
+ el.textContent = inner;
38032
+ range.deleteContents();
38033
+ range.insertNode(el);
38034
+ const after = document.createTextNode("\u200B");
38035
+ el.parentNode?.insertBefore(after, el.nextSibling);
38036
+ const caret = document.createRange();
38037
+ caret.setStart(after, 1);
38038
+ caret.collapse(true);
38039
+ sel.removeAllRanges();
38040
+ sel.addRange(caret);
38041
+ return true;
38042
+ }
38043
+ return false;
38044
+ }, []);
38045
+ const tryBlockMarkdown = React77.useCallback(() => {
38046
+ const root = ref.current;
38047
+ if (!root) return false;
38048
+ const sel = window.getSelection();
38049
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
38050
+ const node = sel.anchorNode;
38051
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
38052
+ const block = closestBlock(node, root);
38053
+ if (block ? block.firstChild !== node : root.firstChild !== node) return false;
38054
+ const prefix = (node.textContent ?? "").slice(0, sel.anchorOffset);
38055
+ const rule = MD_BLOCK_PREFIXES[prefix];
38056
+ if (!rule) return false;
38057
+ if (node.parentElement?.closest("pre, code")) return false;
38058
+ if (rule.kind === "list" && node.parentElement?.closest("li")) return false;
38059
+ node.deleteData(0, prefix.length);
38060
+ const caret = document.createRange();
38061
+ caret.setStart(node, 0);
38062
+ caret.collapse(true);
38063
+ sel.removeAllRanges();
38064
+ sel.addRange(caret);
38065
+ if (rule.kind === "list") {
38066
+ const container = block ?? node.parentElement;
38067
+ const list = document.createElement(rule.command === "insertOrderedList" ? "ol" : "ul");
38068
+ const li = document.createElement("li");
38069
+ if (block) {
38070
+ while (block.firstChild) li.appendChild(block.firstChild);
38071
+ list.appendChild(li);
38072
+ block.replaceWith(list);
38073
+ } else if (container) {
38074
+ li.appendChild(node);
38075
+ list.appendChild(li);
38076
+ container.appendChild(list);
38077
+ }
38078
+ if (!li.firstChild) li.appendChild(document.createElement("br"));
38079
+ const liCaret = document.createRange();
38080
+ liCaret.selectNodeContents(li);
38081
+ liCaret.collapse(true);
38082
+ sel.removeAllRanges();
38083
+ sel.addRange(liCaret);
38084
+ } else {
38085
+ document.execCommand("formatBlock", false, rule.tag);
38086
+ }
38087
+ return true;
38088
+ }, []);
38089
+ const afterEdit = React77.useCallback(() => {
38090
+ const el = ref.current;
38091
+ if (!el) return;
38092
+ tryInlineMarkdown();
38093
+ setEmpty(htmlIsEmpty(el));
38094
+ setToolbar(readToolbarState(el));
38095
+ scheduleEmit();
38096
+ }, [scheduleEmit, tryInlineMarkdown]);
38097
+ const handleKeyDown = React77.useCallback((e) => {
38098
+ if (e.key === " " && tryBlockMarkdown()) {
38099
+ e.preventDefault();
38100
+ afterEdit();
38101
+ }
38102
+ }, [afterEdit, tryBlockMarkdown]);
38103
+ const execInline = React77.useCallback((command) => {
38104
+ document.execCommand(command, false);
38105
+ afterEdit();
38106
+ }, [afterEdit]);
38107
+ const execBlock = React77.useCallback((tag) => {
38108
+ const next = toolbar.block === tag ? "p" : tag;
38109
+ document.execCommand("formatBlock", false, next);
38110
+ afterEdit();
38111
+ }, [afterEdit, toolbar.block]);
38112
+ const execList = React77.useCallback((command) => {
38113
+ document.execCommand(command, false);
38114
+ afterEdit();
38115
+ }, [afterEdit]);
38116
+ const execLink = React77.useCallback(() => {
38117
+ if (toolbar.link) {
38118
+ document.execCommand("unlink", false);
38119
+ afterEdit();
38120
+ return;
38121
+ }
38122
+ const url = window.prompt(t("richTextEditor.linkPrompt"));
38123
+ if (!url) return;
38124
+ const safe = safeUrl(url, ["http://", "https://", "mailto:"]) ?? `https://${url}`;
38125
+ document.execCommand("createLink", false, safe);
38126
+ afterEdit();
38127
+ }, [afterEdit, t, toolbar.link]);
38128
+ const execRule = React77.useCallback(() => {
38129
+ document.execCommand("insertHorizontalRule", false);
38130
+ afterEdit();
38131
+ }, [afterEdit]);
38132
+ if (readOnly) {
38133
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: cn("almadar-rich-text max-w-none", className), children: [
38134
+ /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
38135
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { dangerouslySetInnerHTML: { __html: sanitizeRichHtml(value ?? "") } })
38136
+ ] });
38137
+ }
38138
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: cn("flex flex-col gap-2", className), children: [
38139
+ /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
38140
+ showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
38141
+ exports.Box,
38142
+ {
38143
+ role: "toolbar",
38144
+ "aria-label": t("richTextEditor.editorToolbar"),
38145
+ className: cn(
38146
+ "sticky top-0 z-10 flex flex-wrap items-center gap-0.5 self-start",
38147
+ "rounded-md border border-border bg-background/95 px-1 py-0.5 shadow-sm"
38148
+ ),
38149
+ children: [
38150
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Bold, label: t("richTextEditor.bold"), active: toolbar.bold, onExec: () => execInline("bold") }),
38151
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Italic, label: t("richTextEditor.italic"), active: toolbar.italic, onExec: () => execInline("italic") }),
38152
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Underline, label: t("richTextEditor.underline"), active: toolbar.underline, onExec: () => execInline("underline") }),
38153
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Strikethrough, label: t("richTextEditor.strikethrough"), active: toolbar.strikeThrough, onExec: () => execInline("strikeThrough") }),
38154
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "mx-1 h-5 w-px bg-border" }),
38155
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading1, label: t("richTextEditor.heading1"), active: toolbar.block === "h1", onExec: () => execBlock("h1") }),
38156
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading2, label: t("richTextEditor.heading2"), active: toolbar.block === "h2", onExec: () => execBlock("h2") }),
38157
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading3, label: t("richTextEditor.heading3"), active: toolbar.block === "h3", onExec: () => execBlock("h3") }),
38158
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "mx-1 h-5 w-px bg-border" }),
38159
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.List, label: t("richTextEditor.bulletList"), active: toolbar.bullets, onExec: () => execList("insertUnorderedList") }),
38160
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.ListOrdered, label: t("richTextEditor.numberedList"), active: toolbar.numbers, onExec: () => execList("insertOrderedList") }),
38161
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "mx-1 h-5 w-px bg-border" }),
38162
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
38163
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
38164
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Link, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
38165
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Minus, label: t("richTextEditor.divider"), onExec: execRule })
38166
+ ]
38167
+ }
38168
+ ),
38169
+ /* @__PURE__ */ jsxRuntime.jsx(
38170
+ exports.Box,
38171
+ {
38172
+ ref,
38173
+ contentEditable: true,
38174
+ suppressContentEditableWarning: true,
38175
+ role: "textbox",
38176
+ "aria-multiline": "true",
38177
+ "aria-label": t("richTextEditor.editorSurface"),
38178
+ "data-placeholder": placeholder ?? t("richTextEditor.placeholder"),
38179
+ "data-empty": empty ? "true" : "false",
38180
+ className: cn(
38181
+ "almadar-rich-text max-w-none min-h-[8rem] outline-none",
38182
+ "data-[empty=true]:before:content-[attr(data-placeholder)]",
38183
+ "data-[empty=true]:before:text-muted-foreground/60",
38184
+ "data-[empty=true]:before:pointer-events-none",
38185
+ "data-[empty=true]:before:absolute",
38186
+ "relative cursor-text"
38187
+ ),
38188
+ onInput: afterEdit,
38189
+ onKeyDown: handleKeyDown,
38190
+ onBlur: flushEmit
38191
+ }
38192
+ )
38193
+ ] });
37846
38194
  };
38195
+ exports.RichTextEditor.displayName = "RichTextEditor";
37847
38196
  }
37848
- const seed = block.children?.[0]?.content ?? block.content ?? "";
37849
- return { id: block.id, type, content: seed };
38197
+ });
38198
+ function renderIconInput4(icon, props) {
38199
+ return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
37850
38200
  }
37851
- function BlockMenu({ block, readOnly, onDelete, onDuplicate, onChangeType }) {
38201
+ function DocumentPanel({
38202
+ entity,
38203
+ title,
38204
+ subtitle,
38205
+ value,
38206
+ actions,
38207
+ maxInlineActions,
38208
+ editing = false,
38209
+ titleCommitEvent,
38210
+ contentChangeEvent,
38211
+ editEvent,
38212
+ doneEvent,
38213
+ editLabel,
38214
+ doneLabel,
38215
+ autosaveHint,
38216
+ placeholder,
38217
+ className
38218
+ }) {
38219
+ const eventBus = useEventBus();
37852
38220
  const { t } = hooks.useTranslate();
37853
- const [open, setOpen] = React77.useState(false);
37854
- const ref = React77.useRef(null);
37855
- React77.useEffect(() => {
37856
- if (!open) return;
37857
- function onDocClick(e) {
37858
- if (ref.current && !ref.current.contains(e.target)) {
37859
- setOpen(false);
37860
- }
38221
+ const [titleDraft, setTitleDraft] = React77.useState(null);
38222
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
38223
+ const actionDefs = actions ?? [];
38224
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
38225
+ const inlineActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
38226
+ const menuActions = actionDefs.filter((a) => !inlineActions.includes(a));
38227
+ const fireAction = (action) => {
38228
+ if (!action.event) return;
38229
+ eventBus.emit(`UI:${action.event}`, { id: recordId, row: entity });
38230
+ };
38231
+ const commitTitle = () => {
38232
+ if (!titleCommitEvent) return;
38233
+ const next = (titleDraft ?? "").trim();
38234
+ setTitleDraft(null);
38235
+ if (!next || next === title) return;
38236
+ eventBus.emit(`UI:${titleCommitEvent}`, { title: next, id: recordId });
38237
+ };
38238
+ const untitled = t("documentPanel.untitled") || "Untitled";
38239
+ const titleNode = titleCommitEvent && titleDraft !== null ? /* @__PURE__ */ jsxRuntime.jsx(
38240
+ exports.Input,
38241
+ {
38242
+ value: titleDraft,
38243
+ autoFocus: true,
38244
+ "aria-label": t("common.title"),
38245
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
38246
+ onFocus: (e) => e.currentTarget.select(),
38247
+ onChange: (e) => setTitleDraft(e.target.value),
38248
+ onBlur: commitTitle,
38249
+ onKeyDown: (e) => {
38250
+ if (e.key === "Enter") {
38251
+ e.preventDefault();
38252
+ commitTitle();
38253
+ } else if (e.key === "Escape") {
38254
+ e.preventDefault();
38255
+ setTitleDraft(null);
38256
+ }
38257
+ },
38258
+ "data-testid": "document-title-input"
37861
38259
  }
37862
- document.addEventListener("mousedown", onDocClick);
37863
- return () => document.removeEventListener("mousedown", onDocClick);
37864
- }, [open]);
37865
- if (readOnly) return null;
37866
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { ref, className: "relative", children: [
37867
- /* @__PURE__ */ jsxRuntime.jsx(
37868
- exports.Button,
38260
+ ) : titleCommitEvent ? /* @__PURE__ */ jsxRuntime.jsx(
38261
+ exports.Box,
38262
+ {
38263
+ role: "button",
38264
+ tabIndex: 0,
38265
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
38266
+ onClick: () => setTitleDraft(title ?? ""),
38267
+ onKeyDown: (e) => {
38268
+ if (e.key === "Enter" || e.key === " ") {
38269
+ e.preventDefault();
38270
+ setTitleDraft(title ?? "");
38271
+ }
38272
+ },
38273
+ "data-testid": "document-title-editable",
38274
+ children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h2", weight: "bold", className: cn(!title && "text-muted-foreground"), children: title || untitled })
38275
+ }
38276
+ ) : /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "h2", weight: "bold", children: title || untitled });
38277
+ const emitWithId = (event) => () => {
38278
+ if (event) eventBus.emit(`UI:${event}`, { id: recordId });
38279
+ };
38280
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
38281
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "sm", className: "justify-between items-start", children: [
38282
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
38283
+ titleNode,
38284
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: subtitle }) : null
38285
+ ] }),
38286
+ /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38287
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
38288
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
38289
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38290
+ editEvent && /* @__PURE__ */ jsxRuntime.jsxs(exports.Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
38291
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "edit", size: "xs", className: "mr-1" }),
38292
+ editLabel ?? (t("documentPanel.edit") || "Edit")
38293
+ ] }),
38294
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
38295
+ exports.Button,
38296
+ {
38297
+ variant: "primary",
38298
+ size: "sm",
38299
+ onClick: () => fireAction(action),
38300
+ "data-testid": `action-${action.event}`,
38301
+ children: [
38302
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
38303
+ action.label
38304
+ ]
38305
+ },
38306
+ idx
38307
+ )),
38308
+ menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
38309
+ exports.Menu,
38310
+ {
38311
+ position: "bottom-end",
38312
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
38313
+ items: menuActions.map((action) => ({
38314
+ label: action.label,
38315
+ icon: action.icon,
38316
+ variant: action.variant === "danger" ? "danger" : "default",
38317
+ onClick: () => fireAction(action)
38318
+ }))
38319
+ }
38320
+ )
38321
+ ] }) })
38322
+ ] }),
38323
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
38324
+ exports.Box,
37869
38325
  {
37870
- type: "button",
37871
- variant: "ghost",
37872
- "aria-label": t("richBlockEditor.blockActions"),
38326
+ onClick: emitWithId(editEvent),
37873
38327
  className: cn(
37874
- "inline-flex items-center justify-center",
37875
- "h-6 w-6 rounded-sm p-0 gap-0",
37876
- "text-muted-foreground hover:bg-muted hover:text-foreground",
37877
- "opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
37878
- "transition-opacity"
38328
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
38329
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
37879
38330
  ),
37880
- onClick: () => setOpen((v) => !v),
37881
- children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", className: "w-3.5 h-3.5" })
37882
- }
37883
- ),
37884
- open && /* @__PURE__ */ jsxRuntime.jsxs(
37885
- exports.Box,
37886
- {
37887
- role: "menu",
37888
- className: cn(
37889
- "absolute right-0 z-10 mt-1 w-44",
37890
- "rounded-container border border-border bg-popover shadow-elevation-popover",
37891
- "py-1 text-sm"
37892
- ),
37893
- children: [
37894
- /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t(BLOCK_TYPE_LABEL_KEY[block.type]) }),
37895
- /* @__PURE__ */ jsxRuntime.jsxs(
37896
- exports.Button,
37897
- {
37898
- type: "button",
37899
- variant: "ghost",
37900
- role: "menuitem",
37901
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
37902
- onClick: () => {
37903
- onDuplicate();
37904
- setOpen(false);
37905
- },
37906
- children: [
37907
- /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "plus", className: "w-3.5 h-3.5" }),
37908
- " ",
37909
- t("richBlockEditor.duplicate")
37910
- ]
37911
- }
37912
- ),
37913
- /* @__PURE__ */ jsxRuntime.jsxs(
37914
- exports.Button,
37915
- {
37916
- type: "button",
37917
- variant: "ghost",
37918
- role: "menuitem",
37919
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-destructive hover:bg-muted justify-start rounded-none",
37920
- onClick: () => {
37921
- onDelete();
37922
- setOpen(false);
37923
- },
37924
- children: [
37925
- /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "trash", className: "w-3.5 h-3.5" }),
37926
- " ",
37927
- t("common.delete")
37928
- ]
37929
- }
37930
- ),
37931
- CHANGEABLE_TYPES.includes(block.type) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
37932
- /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "my-1 border-t border-border" }),
37933
- /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t("richBlockEditor.turnInto") }),
37934
- CHANGEABLE_TYPES.filter((bt) => bt !== block.type).map((bt) => /* @__PURE__ */ jsxRuntime.jsx(
37935
- exports.Button,
37936
- {
37937
- type: "button",
37938
- variant: "ghost",
37939
- role: "menuitem",
37940
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
37941
- onClick: () => {
37942
- onChangeType(bt);
37943
- setOpen(false);
37944
- },
37945
- children: t(BLOCK_TYPE_LABEL_KEY[bt])
37946
- },
37947
- bt
37948
- ))
37949
- ] })
37950
- ]
38331
+ "data-testid": "document-body",
38332
+ children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
37951
38333
  }
37952
38334
  )
37953
- ] });
38335
+ ] }) });
37954
38336
  }
37955
- function Editable({
37956
- tag,
37957
- value,
37958
- readOnly,
37959
- placeholder,
37960
- className,
37961
- ariaLabel,
37962
- onValueChange
37963
- }) {
37964
- const ref = React77.useRef(null);
37965
- React77.useEffect(() => {
37966
- const el = ref.current;
37967
- if (!el) return;
37968
- const isFocused = document.activeElement === el;
37969
- if (!isFocused && el.textContent !== value) {
37970
- el.textContent = value;
37971
- }
37972
- }, [value]);
37973
- const handleInput = React77.useCallback(
37974
- (e) => {
37975
- onValueChange(e.currentTarget.textContent ?? "");
37976
- },
37977
- [onValueChange]
37978
- );
37979
- return /* @__PURE__ */ jsxRuntime.jsx(
37980
- exports.Box,
37981
- {
37982
- as: tag,
37983
- ref,
37984
- contentEditable: !readOnly,
37985
- suppressContentEditableWarning: true,
37986
- role: readOnly ? void 0 : "textbox",
37987
- "aria-label": ariaLabel,
37988
- "aria-multiline": "true",
37989
- "data-placeholder": placeholder,
37990
- className: cn(
37991
- "outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-sm",
37992
- "empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground/60",
37993
- className
37994
- ),
37995
- onInput: handleInput
37996
- }
37997
- );
38337
+ var init_DocumentPanel = __esm({
38338
+ "components/core/molecules/DocumentPanel.tsx"() {
38339
+ "use client";
38340
+ init_cn();
38341
+ init_useEventBus();
38342
+ init_Box();
38343
+ init_Stack();
38344
+ init_Typography();
38345
+ init_Button();
38346
+ init_Card();
38347
+ init_Icon();
38348
+ init_Input();
38349
+ init_Menu();
38350
+ init_RichTextEditor();
38351
+ }
38352
+ });
38353
+ function renderIconInput5(icon, props) {
38354
+ return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
37998
38355
  }
37999
- function BlockRow({
38000
- block,
38001
- readOnly,
38002
- showAffordances,
38003
- placeholder,
38004
- onUpdate,
38005
- onDelete,
38006
- onDuplicate,
38007
- onChangeType
38356
+ function fieldName(field) {
38357
+ return field.name ?? field.key ?? "";
38358
+ }
38359
+ function relationLabel(value) {
38360
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
38361
+ for (const key of ["name", "title", "label"]) {
38362
+ const candidate = value[key];
38363
+ if (typeof candidate === "string" && candidate !== "") return candidate;
38364
+ }
38365
+ const id = value.id;
38366
+ return id !== void 0 && id !== null ? String(id) : null;
38367
+ }
38368
+ function fieldKind(field, value) {
38369
+ if (field.kind) return field.kind;
38370
+ if (field.options && field.options.length > 0) return "select";
38371
+ if (typeof value === "boolean" || field.format === "boolean") return "boolean";
38372
+ if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
38373
+ return "text";
38374
+ }
38375
+ function DocumentDetails({
38376
+ entity,
38377
+ fields,
38378
+ metaCommitEvent,
38379
+ title,
38380
+ className
38008
38381
  }) {
38382
+ const eventBus = useEventBus();
38009
38383
  const { t } = hooks.useTranslate();
38010
- const setContent = React77.useCallback(
38011
- (next) => onUpdate((b) => ({ ...b, content: next })),
38012
- [onUpdate]
38013
- );
38014
- const setMetadata = React77.useCallback(
38015
- (key, value) => onUpdate((b) => ({
38016
- ...b,
38017
- metadata: { ...b.metadata ?? {}, [key]: value }
38018
- })),
38019
- [onUpdate]
38020
- );
38021
- const setChildContent = React77.useCallback(
38022
- (childId, next) => onUpdate((b) => ({
38023
- ...b,
38024
- children: (b.children ?? []).map(
38025
- (c) => c.id === childId ? { ...c, content: next } : c
38026
- )
38027
- })),
38028
- [onUpdate]
38029
- );
38030
- const addListItem = React77.useCallback(
38031
- () => onUpdate((b) => ({
38032
- ...b,
38033
- children: [
38034
- ...b.children ?? [],
38035
- { id: nextBlockId("li"), type: "paragraph", content: "" }
38036
- ]
38037
- })),
38038
- [onUpdate]
38039
- );
38040
- const removeListItem = React77.useCallback(
38041
- (childId) => onUpdate((b) => {
38042
- const remaining = (b.children ?? []).filter((c) => c.id !== childId);
38043
- return {
38044
- ...b,
38045
- children: remaining.length === 0 ? [{ id: nextBlockId("li"), type: "paragraph", content: "" }] : remaining
38046
- };
38047
- }),
38048
- [onUpdate]
38049
- );
38050
- const renderBody = () => {
38051
- switch (block.type) {
38052
- case "heading-1":
38053
- return /* @__PURE__ */ jsxRuntime.jsx(
38054
- Editable,
38055
- {
38056
- tag: "h1",
38057
- value: block.content ?? "",
38058
- readOnly,
38059
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading1"),
38060
- ariaLabel: t("richBlockEditor.aria.heading1Block"),
38061
- className: "text-3xl font-bold leading-tight",
38062
- onValueChange: setContent
38063
- }
38064
- );
38065
- case "heading-2":
38066
- return /* @__PURE__ */ jsxRuntime.jsx(
38067
- Editable,
38068
- {
38069
- tag: "h2",
38070
- value: block.content ?? "",
38071
- readOnly,
38072
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading2"),
38073
- ariaLabel: t("richBlockEditor.aria.heading2Block"),
38074
- className: "text-2xl font-semibold leading-tight",
38075
- onValueChange: setContent
38076
- }
38077
- );
38078
- case "heading-3":
38079
- return /* @__PURE__ */ jsxRuntime.jsx(
38080
- Editable,
38081
- {
38082
- tag: "h3",
38083
- value: block.content ?? "",
38084
- readOnly,
38085
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading3"),
38086
- ariaLabel: t("richBlockEditor.aria.heading3Block"),
38087
- className: "text-xl font-semibold leading-tight",
38088
- onValueChange: setContent
38089
- }
38090
- );
38091
- case "quote":
38092
- return /* @__PURE__ */ jsxRuntime.jsx(
38093
- Editable,
38094
- {
38095
- tag: "blockquote",
38096
- value: block.content ?? "",
38097
- readOnly,
38098
- placeholder: placeholder ?? t("richBlockEditor.placeholder.quote"),
38099
- ariaLabel: t("richBlockEditor.aria.quoteBlock"),
38100
- className: "border-l-4 border-primary/60 pl-4 italic text-muted-foreground",
38101
- onValueChange: setContent
38102
- }
38103
- );
38104
- case "code":
38105
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "rounded-md border border-border bg-muted/40", children: [
38106
- /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "flex items-center justify-between border-b border-border px-3 py-1 text-xs text-muted-foreground", children: [
38107
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { as: "span", variant: "caption", className: "uppercase tracking-wide", children: t("richBlockEditor.blockType.code") }),
38108
- !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
38109
- exports.Input,
38110
- {
38111
- inputType: "text",
38112
- value: String(block.metadata?.language ?? "plaintext"),
38113
- "aria-label": t("richBlockEditor.aria.codeLanguage"),
38114
- className: cn(
38115
- "h-6 w-32 rounded-sm border border-border bg-background",
38116
- "px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
38117
- ),
38118
- onChange: (e) => setMetadata("language", e.target.value)
38119
- }
38120
- ),
38121
- readOnly && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { as: "span", variant: "caption", className: "text-xs", children: String(block.metadata?.language ?? "plaintext") })
38122
- ] }),
38123
- /* @__PURE__ */ jsxRuntime.jsx(
38124
- Editable,
38125
- {
38126
- tag: "pre",
38127
- value: block.content ?? "",
38128
- readOnly,
38129
- placeholder: placeholder ?? t("richBlockEditor.placeholder.code"),
38130
- ariaLabel: t("richBlockEditor.aria.codeBlock"),
38131
- className: "block whitespace-pre-wrap p-3 font-mono text-sm leading-relaxed",
38132
- onValueChange: setContent
38133
- }
38134
- )
38135
- ] });
38136
- case "divider":
38137
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Divider, { className: "my-2" });
38138
- case "image": {
38139
- const url = String(block.metadata?.url ?? "");
38140
- const caption = String(block.metadata?.caption ?? "");
38141
- const imgProps = {
38142
- src: url,
38143
- alt: caption || t("richBlockEditor.embeddedImage"),
38144
- className: "max-h-96 w-full rounded-md border border-border object-contain"
38145
- };
38146
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "space-y-2", children: [
38147
- url ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { as: "img", ...imgProps }) : /* @__PURE__ */ jsxRuntime.jsxs(
38148
- exports.Box,
38149
- {
38150
- className: cn(
38151
- "flex h-32 items-center justify-center",
38152
- "rounded-md border border-dashed border-border",
38153
- "text-sm text-muted-foreground"
38154
- ),
38155
- children: [
38156
- /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "image", className: "mr-2 w-4 h-4" }),
38157
- " ",
38158
- t("richBlockEditor.noImageUrl")
38159
- ]
38160
- }
38161
- ),
38162
- !readOnly && /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "flex flex-col gap-2 sm:flex-row", children: [
38163
- /* @__PURE__ */ jsxRuntime.jsx(
38164
- exports.Input,
38165
- {
38166
- inputType: "url",
38167
- value: url,
38168
- placeholder: "https://example.com/image.png",
38169
- "aria-label": t("richBlockEditor.aria.imageUrl"),
38170
- className: cn(
38171
- "h-8 flex-1 rounded-sm border border-border bg-background",
38172
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
38173
- ),
38174
- onChange: (e) => setMetadata("url", e.target.value)
38175
- }
38176
- ),
38177
- /* @__PURE__ */ jsxRuntime.jsx(
38178
- exports.Input,
38179
- {
38180
- inputType: "text",
38181
- value: caption,
38182
- placeholder: t("richBlockEditor.placeholder.caption"),
38183
- "aria-label": t("richBlockEditor.aria.imageCaption"),
38184
- className: cn(
38185
- "h-8 flex-1 rounded-sm border border-border bg-background",
38186
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
38187
- ),
38188
- onChange: (e) => setMetadata("caption", e.target.value)
38189
- }
38190
- )
38191
- ] }),
38192
- readOnly && caption && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", className: "text-center text-muted-foreground", children: caption })
38193
- ] });
38384
+ const [fieldDraft, setFieldDraft] = React77.useState(null);
38385
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
38386
+ const fieldDefs = (fields ?? []).filter((f3) => fieldName(f3) !== "");
38387
+ if (fieldDefs.length === 0) return null;
38388
+ const commitField = (name, next, current) => {
38389
+ setFieldDraft(null);
38390
+ if (!metaCommitEvent || next === current) return;
38391
+ eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
38392
+ };
38393
+ const renderValue = (field) => {
38394
+ const name = fieldName(field);
38395
+ const raw = getNestedValue(entity ?? {}, name);
38396
+ const kind = fieldKind(field, raw);
38397
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
38398
+ if (kind === "boolean") {
38399
+ return /* @__PURE__ */ jsxRuntime.jsx(
38400
+ exports.Switch,
38401
+ {
38402
+ checked: Boolean(raw),
38403
+ disabled: !metaCommitEvent,
38404
+ "aria-label": label,
38405
+ onChange: (checked) => commitField(name, checked, Boolean(raw))
38406
+ }
38407
+ );
38408
+ }
38409
+ if (kind === "select") {
38410
+ return /* @__PURE__ */ jsxRuntime.jsx(
38411
+ exports.Select,
38412
+ {
38413
+ value: raw !== void 0 && raw !== null ? String(raw) : "",
38414
+ disabled: !metaCommitEvent,
38415
+ "aria-label": label,
38416
+ className: "h-8 w-full",
38417
+ options: (field.options ?? []).map((opt) => ({ value: opt, label: humanizeEnumValue(opt) })),
38418
+ onChange: (e) => commitField(name, e.target.value, raw)
38419
+ }
38420
+ );
38421
+ }
38422
+ if (kind === "readonly" || !metaCommitEvent) {
38423
+ if (Array.isArray(raw)) {
38424
+ if (raw.length === 0) {
38425
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: "\u2014" });
38426
+ }
38427
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
38194
38428
  }
38195
- case "bullet-list":
38196
- case "numbered-list": {
38197
- const items = block.children ?? [];
38198
- return /* @__PURE__ */ jsxRuntime.jsxs(
38199
- exports.Box,
38200
- {
38201
- as: block.type === "bullet-list" ? "ul" : "ol",
38202
- className: cn(
38203
- "space-y-1 pl-6",
38204
- block.type === "bullet-list" ? "list-disc" : "list-decimal"
38205
- ),
38206
- children: [
38207
- items.map((child) => /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { as: "li", className: "group/item flex items-start gap-2", children: [
38208
- /* @__PURE__ */ jsxRuntime.jsx(
38209
- Editable,
38210
- {
38211
- tag: "span",
38212
- value: child.content ?? "",
38213
- readOnly,
38214
- placeholder: t("richBlockEditor.placeholder.listItem"),
38215
- ariaLabel: t("richBlockEditor.aria.listItem"),
38216
- className: "inline-block min-w-[1ch] flex-1",
38217
- onValueChange: (next) => setChildContent(child.id, next)
38218
- }
38219
- ),
38220
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(
38221
- exports.Button,
38222
- {
38223
- type: "button",
38224
- variant: "ghost",
38225
- "aria-label": t("richBlockEditor.aria.removeListItem"),
38226
- className: cn(
38227
- "h-5 w-5 shrink-0 rounded-sm text-muted-foreground p-0 gap-0",
38228
- "opacity-0 group-hover/item:opacity-100 hover:bg-muted hover:text-foreground"
38229
- ),
38230
- onClick: () => removeListItem(child.id),
38231
- children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "trash", className: "w-3 h-3" })
38232
- }
38233
- )
38234
- ] }, child.id)),
38235
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { as: "li", className: "list-none pl-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
38236
- exports.Button,
38237
- {
38238
- type: "button",
38239
- variant: "ghost",
38240
- className: cn(
38241
- "inline-flex items-center gap-1 text-xs text-muted-foreground",
38242
- "hover:text-foreground p-0 h-auto"
38243
- ),
38244
- onClick: addListItem,
38245
- children: [
38246
- /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "plus", className: "w-3 h-3" }),
38247
- " ",
38248
- t("richBlockEditor.addItem")
38249
- ]
38250
- }
38251
- ) })
38252
- ]
38429
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
38430
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", className: "break-words", children: shown2 });
38431
+ }
38432
+ if (fieldDraft?.name === name) {
38433
+ return /* @__PURE__ */ jsxRuntime.jsx(
38434
+ exports.Input,
38435
+ {
38436
+ value: fieldDraft.value,
38437
+ autoFocus: true,
38438
+ "aria-label": label,
38439
+ className: "h-8 w-full",
38440
+ onChange: (e) => setFieldDraft({ name, value: e.target.value }),
38441
+ onBlur: () => commitField(name, fieldDraft.value.trim(), raw),
38442
+ onKeyDown: (e) => {
38443
+ if (e.key === "Enter") {
38444
+ e.preventDefault();
38445
+ commitField(name, fieldDraft.value.trim(), raw);
38446
+ } else if (e.key === "Escape") {
38447
+ e.preventDefault();
38448
+ setFieldDraft(null);
38449
+ }
38450
+ },
38451
+ "data-testid": `document-property-input-${name}`
38452
+ }
38453
+ );
38454
+ }
38455
+ const shown = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
38456
+ return /* @__PURE__ */ jsxRuntime.jsx(
38457
+ exports.Box,
38458
+ {
38459
+ role: "button",
38460
+ tabIndex: 0,
38461
+ className: cn(
38462
+ "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
38463
+ (raw === void 0 || raw === null || raw === "") && "text-muted-foreground"
38464
+ ),
38465
+ onClick: () => setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" }),
38466
+ onKeyDown: (e) => {
38467
+ if (e.key === "Enter" || e.key === " ") {
38468
+ e.preventDefault();
38469
+ setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" });
38253
38470
  }
38254
- );
38471
+ },
38472
+ "data-testid": `document-property-${name}`,
38473
+ children: /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", className: "break-words", children: shown })
38255
38474
  }
38256
- case "paragraph":
38257
- default:
38258
- return /* @__PURE__ */ jsxRuntime.jsx(
38259
- Editable,
38260
- {
38261
- tag: "p",
38262
- value: block.content ?? "",
38263
- readOnly,
38264
- placeholder: placeholder ?? t("richBlockEditor.placeholder.paragraph"),
38265
- ariaLabel: t("richBlockEditor.aria.paragraphBlock"),
38266
- className: "leading-7",
38267
- onValueChange: setContent
38268
- }
38269
- );
38270
- }
38475
+ );
38271
38476
  };
38272
- return /* @__PURE__ */ jsxRuntime.jsxs(
38273
- exports.Box,
38274
- {
38275
- className: cn(
38276
- "group relative flex items-start gap-2 rounded-sm",
38277
- "px-2 py-1 hover:bg-muted/30"
38278
- ),
38279
- "data-block-id": block.id,
38280
- "data-block-type": block.type,
38281
- children: [
38282
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "flex w-8 shrink-0 items-center pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(
38283
- BlockMenu,
38284
- {
38285
- block,
38286
- readOnly,
38287
- onDelete,
38288
- onDuplicate,
38289
- onChangeType
38290
- }
38291
- ) }),
38292
- /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "min-w-0 flex-1", children: renderBody() })
38293
- ]
38294
- }
38295
- );
38477
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { variant: "bordered", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: "p-4", children: [
38478
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "secondary", weight: "medium", className: "uppercase tracking-wide", children: title ?? (t("documentDetails.title") || "Details") }),
38479
+ /* @__PURE__ */ jsxRuntime.jsx(exports.VStack, { gap: "sm", children: fieldDefs.map((field) => {
38480
+ const name = fieldName(field);
38481
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
38482
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", children: [
38483
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "xs", className: "items-center", children: [
38484
+ field.icon && renderIconInput5(field.icon, { size: "xs", className: "text-muted-foreground" }),
38485
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: label })
38486
+ ] }),
38487
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "min-w-0", children: renderValue(field) })
38488
+ ] }, name);
38489
+ }) })
38490
+ ] }) });
38296
38491
  }
38297
- var TOOLBAR_ENTRIES, BLOCK_TYPE_LABEL_KEY, CHANGEABLE_TYPES, _idSeq, BLOCK_TYPES; exports.RichBlockEditor = void 0;
38298
- var init_RichBlockEditor = __esm({
38299
- "components/core/molecules/RichBlockEditor.tsx"() {
38492
+ var init_DocumentDetails = __esm({
38493
+ "components/core/molecules/DocumentDetails.tsx"() {
38300
38494
  "use client";
38301
38495
  init_cn();
38302
- init_Card();
38303
- init_Typography();
38304
- init_Button();
38496
+ init_format();
38497
+ init_getNestedValue();
38498
+ init_useEventBus();
38305
38499
  init_Box();
38306
- init_Divider();
38307
- init_Input();
38500
+ init_Stack();
38501
+ init_Typography();
38502
+ init_Badge();
38503
+ init_Card();
38308
38504
  init_Icon();
38309
- init_useEventBus();
38310
- TOOLBAR_ENTRIES = [
38311
- { type: "paragraph", labelKey: "richBlockEditor.toolbar.text", icon: LucideIcons2.Type },
38312
- { type: "heading-1", labelKey: "richBlockEditor.toolbar.h1", icon: LucideIcons2.Heading1 },
38313
- { type: "heading-2", labelKey: "richBlockEditor.toolbar.h2", icon: LucideIcons2.Heading2 },
38314
- { type: "heading-3", labelKey: "richBlockEditor.toolbar.h3", icon: LucideIcons2.Heading3 },
38315
- { type: "bullet-list", labelKey: "richBlockEditor.toolbar.bulletList", icon: LucideIcons2.List },
38316
- { type: "numbered-list", labelKey: "richBlockEditor.toolbar.numbered", icon: LucideIcons2.ListOrdered },
38317
- { type: "quote", labelKey: "richBlockEditor.toolbar.quote", icon: LucideIcons2.Quote },
38318
- { type: "code", labelKey: "richBlockEditor.toolbar.code", icon: LucideIcons2.Code },
38319
- { type: "divider", labelKey: "richBlockEditor.toolbar.divider", icon: LucideIcons2.Minus },
38320
- { type: "image", labelKey: "richBlockEditor.toolbar.image", icon: LucideIcons2.Image }
38321
- ];
38322
- BLOCK_TYPE_LABEL_KEY = {
38323
- paragraph: "richBlockEditor.blockType.paragraph",
38324
- "heading-1": "richBlockEditor.blockType.heading1",
38325
- "heading-2": "richBlockEditor.blockType.heading2",
38326
- "heading-3": "richBlockEditor.blockType.heading3",
38327
- "bullet-list": "richBlockEditor.blockType.bulletList",
38328
- "numbered-list": "richBlockEditor.blockType.numberedList",
38329
- quote: "richBlockEditor.blockType.quote",
38330
- code: "richBlockEditor.blockType.code",
38331
- divider: "richBlockEditor.blockType.divider",
38332
- image: "richBlockEditor.blockType.image"
38333
- };
38334
- CHANGEABLE_TYPES = [
38335
- "paragraph",
38336
- "heading-1",
38337
- "heading-2",
38338
- "heading-3",
38339
- "bullet-list",
38340
- "numbered-list",
38341
- "quote",
38342
- "code"
38343
- ];
38344
- _idSeq = 0;
38345
- BLOCK_TYPES = /* @__PURE__ */ new Set([
38346
- "paragraph",
38347
- "heading-1",
38348
- "heading-2",
38349
- "heading-3",
38350
- "bullet-list",
38351
- "numbered-list",
38352
- "quote",
38353
- "code",
38354
- "divider",
38355
- "image"
38356
- ]);
38357
- exports.RichBlockEditor = ({
38358
- initialBlocks,
38359
- onChange,
38360
- changeEvent,
38361
- readOnly = false,
38362
- placeholder,
38363
- enableBlocks = true,
38364
- showToolbar = true,
38365
- className
38366
- }) => {
38367
- const { t } = hooks.useTranslate();
38368
- const [blocks, setBlocks] = React77.useState(
38369
- () => normalizeBlocks(initialBlocks)
38370
- );
38371
- const onChangeRef = React77.useRef(onChange);
38372
- React77.useEffect(() => {
38373
- onChangeRef.current = onChange;
38374
- }, [onChange]);
38375
- const eventBus = useEventBus();
38376
- const changeEventRef = React77.useRef(changeEvent);
38377
- React77.useEffect(() => {
38378
- changeEventRef.current = changeEvent;
38379
- }, [changeEvent]);
38380
- const commit = React77.useCallback((next) => {
38381
- setBlocks(next);
38382
- onChangeRef.current?.(next);
38383
- const evt = changeEventRef.current;
38384
- if (evt) eventBus.emit(`UI:${evt}`, { blocks: next });
38385
- }, [eventBus]);
38386
- const handleAppend = React77.useCallback(
38387
- (type) => {
38388
- if (readOnly) return;
38389
- commit([...blocks, createBlock(type)]);
38390
- },
38391
- [blocks, commit, readOnly]
38392
- );
38393
- const handleUpdate = React77.useCallback(
38394
- (id, updater) => {
38395
- commit(replaceBlock(blocks, id, updater));
38396
- },
38397
- [blocks, commit]
38398
- );
38399
- const handleDelete = React77.useCallback(
38400
- (id) => {
38401
- const next = removeBlock(blocks, id);
38402
- commit(next.length > 0 ? next : [createBlock("paragraph")]);
38403
- },
38404
- [blocks, commit]
38405
- );
38406
- const handleDuplicate = React77.useCallback(
38407
- (id) => {
38408
- const target = blocks.find((b) => b.id === id);
38409
- if (!target) return;
38410
- commit(insertAfter(blocks, id, duplicateBlock(target)));
38411
- },
38412
- [blocks, commit]
38413
- );
38414
- const handleChangeType = React77.useCallback(
38415
- (id, type) => {
38416
- commit(
38417
- replaceBlock(blocks, id, (b) => changeBlockType(b, type))
38418
- );
38419
- },
38420
- [blocks, commit]
38421
- );
38422
- return /* @__PURE__ */ jsxRuntime.jsxs(
38423
- exports.Card,
38424
- {
38425
- variant: "bordered",
38426
- padding: "none",
38427
- className: cn("flex flex-col text-card-foreground", className),
38428
- children: [
38429
- enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
38430
- exports.Box,
38431
- {
38432
- role: "toolbar",
38433
- "aria-label": t("richBlockEditor.editorToolbar"),
38434
- className: cn(
38435
- "flex flex-wrap items-center gap-1",
38436
- "border-b border-border bg-muted/30 px-2 py-2"
38437
- ),
38438
- children: TOOLBAR_ENTRIES.map((entry) => {
38439
- const Icon2 = entry.icon;
38440
- const entryLabel = t(entry.labelKey);
38441
- return /* @__PURE__ */ jsxRuntime.jsxs(
38442
- exports.Button,
38443
- {
38444
- type: "button",
38445
- variant: "ghost",
38446
- size: "sm",
38447
- "aria-label": t("richBlockEditor.insertEntry", { label: entryLabel }),
38448
- title: entryLabel,
38449
- onClick: () => handleAppend(entry.type),
38450
- children: [
38451
- /* @__PURE__ */ jsxRuntime.jsx(Icon2, { size: 14 }),
38452
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { as: "span", variant: "caption", className: "ml-1 hidden text-xs sm:inline", children: entryLabel })
38453
- ]
38454
- },
38455
- entry.type
38456
- );
38457
- })
38458
- }
38459
- ),
38460
- /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "flex flex-col gap-1 px-3 py-3", children: blocks.map((block) => /* @__PURE__ */ jsxRuntime.jsx(
38461
- BlockRow,
38462
- {
38463
- block,
38464
- readOnly,
38465
- showAffordances: enableBlocks,
38466
- placeholder,
38467
- onUpdate: (updater) => handleUpdate(block.id, updater),
38468
- onDelete: () => handleDelete(block.id),
38469
- onDuplicate: () => handleDuplicate(block.id),
38470
- onChangeType: (type) => handleChangeType(block.id, type)
38471
- },
38472
- block.id
38473
- )) })
38474
- ]
38475
- }
38476
- );
38477
- };
38478
- exports.RichBlockEditor.displayName = "RichBlockEditor";
38505
+ init_Input();
38506
+ init_Select();
38507
+ init_Switch();
38479
38508
  }
38480
38509
  });
38481
38510
  function collectInitiallyCollapsed(nodes, acc) {
@@ -43149,7 +43178,9 @@ var init_molecules2 = __esm({
43149
43178
  init_QrScanner();
43150
43179
  init_OptionConstraintGroup();
43151
43180
  init_PositionedCanvas();
43152
- init_RichBlockEditor();
43181
+ init_RichTextEditor();
43182
+ init_DocumentPanel();
43183
+ init_DocumentDetails();
43153
43184
  init_ReplyTree();
43154
43185
  init_BranchingLogicBuilder();
43155
43186
  init_VersionDiff();
@@ -43649,8 +43680,8 @@ var init_DataTable = __esm({
43649
43680
  DataTable.displayName = "DataTable";
43650
43681
  }
43651
43682
  });
43652
- function getBadgeVariant(fieldName, value) {
43653
- const name = fieldName.toLowerCase();
43683
+ function getBadgeVariant(fieldName2, value) {
43684
+ const name = fieldName2.toLowerCase();
43654
43685
  const val = String(value).toLowerCase();
43655
43686
  if (name.includes("status")) {
43656
43687
  if (val.includes("complete") || val.includes("done") || val.includes("active"))
@@ -43666,12 +43697,12 @@ function getBadgeVariant(fieldName, value) {
43666
43697
  }
43667
43698
  return "default";
43668
43699
  }
43669
- function formatFieldValue2(value, fieldName) {
43700
+ function formatFieldValue2(value, fieldName2) {
43670
43701
  if (typeof value === "number") {
43671
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
43702
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
43672
43703
  return `${value}%`;
43673
43704
  }
43674
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
43705
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
43675
43706
  return `$${value.toLocaleString()}`;
43676
43707
  }
43677
43708
  return value.toLocaleString();
@@ -43681,7 +43712,7 @@ function formatFieldValue2(value, fieldName) {
43681
43712
  }
43682
43713
  return String(value);
43683
43714
  }
43684
- function renderRichFieldValue(value, fieldName, fieldType, meta) {
43715
+ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43685
43716
  if (value === void 0 || value === null) return "\u2014";
43686
43717
  const str2 = String(value);
43687
43718
  switch (fieldType) {
@@ -43692,7 +43723,7 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
43692
43723
  "img",
43693
43724
  {
43694
43725
  src: str2,
43695
- alt: formatFieldLabel(fieldName),
43726
+ alt: formatFieldLabel(fieldName2),
43696
43727
  className: "max-w-full max-h-64 rounded-md object-contain",
43697
43728
  loading: "lazy"
43698
43729
  }
@@ -43827,9 +43858,9 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
43827
43858
  return /* @__PURE__ */ jsxRuntime.jsx("a", { href: `tel:${str2}`, className: "text-primary hover:underline", children: str2 });
43828
43859
  default:
43829
43860
  if (meta?.values && meta.values.length > 0 && meta.values.includes(str2)) {
43830
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: getBadgeVariant(fieldName, str2), children: humanizeEnumValue(str2) });
43861
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: getBadgeVariant(fieldName2, str2), children: humanizeEnumValue(str2) });
43831
43862
  }
43832
- return formatFieldValue2(value, fieldName);
43863
+ return formatFieldValue2(value, fieldName2);
43833
43864
  }
43834
43865
  }
43835
43866
  function normalizeFieldDefs(fields) {
@@ -44299,16 +44330,11 @@ var init_DetailPanel = __esm({
44299
44330
  footer
44300
44331
  ] })
44301
44332
  ] }) });
44302
- return /* @__PURE__ */ jsxRuntime.jsx(
44303
- exports.Box,
44304
- {
44305
- className: cn(
44306
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
44307
- className
44308
- ),
44309
- children: content
44310
- }
44311
- );
44333
+ if (!slideOver) {
44334
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className, children: content });
44335
+ }
44336
+ const panel = /* @__PURE__ */ jsxRuntime.jsx(exports.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 });
44337
+ return typeof document === "undefined" ? panel : reactDom.createPortal(panel, document.body);
44312
44338
  };
44313
44339
  exports.DetailPanel.displayName = "DetailPanel";
44314
44340
  }
@@ -44677,7 +44703,21 @@ var init_Form = __esm({
44677
44703
  const normalizedInitialData = React77__namespace.default.useMemo(() => {
44678
44704
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
44679
44705
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
44680
- return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
44706
+ const merged = entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
44707
+ const toId = (value) => {
44708
+ if (value === null || value === void 0 || typeof value !== "object" || value instanceof Date) return value;
44709
+ if (Array.isArray(value)) {
44710
+ return value.map(
44711
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && item.id !== void 0 && item.id !== null ? String(item.id) : item
44712
+ );
44713
+ }
44714
+ return value.id !== void 0 && value.id !== null ? String(value.id) : value;
44715
+ };
44716
+ const normalized = {};
44717
+ for (const [key, value] of Object.entries(merged)) {
44718
+ normalized[key] = key === "id" ? value : toId(value);
44719
+ }
44720
+ return normalized;
44681
44721
  }, [entity, initialData]);
44682
44722
  const entityDerivedFields = React77__namespace.default.useMemo(() => {
44683
44723
  if (fields && fields.length > 0) return void 0;
@@ -44806,8 +44846,8 @@ var init_Form = __esm({
44806
44846
  checkViolations(name, newFormData);
44807
44847
  };
44808
44848
  const isFieldVisible = React77__namespace.default.useCallback(
44809
- (fieldName) => {
44810
- const condition = conditionalFields[fieldName];
44849
+ (fieldName2) => {
44850
+ const condition = conditionalFields[fieldName2];
44811
44851
  if (!condition) return true;
44812
44852
  return Boolean(evaluateFormExpression(condition, evalContext));
44813
44853
  },
@@ -44853,9 +44893,9 @@ var init_Form = __esm({
44853
44893
  };
44854
44894
  const handleInvalid = (e) => {
44855
44895
  const target = e.target;
44856
- const fieldName = target.getAttribute("data-field-name") ?? target.name ?? "";
44896
+ const fieldName2 = target.getAttribute("data-field-name") ?? target.name ?? "";
44857
44897
  const fieldMessage = target.validationMessage || "Invalid value";
44858
- debug("forms", "invalid", { mode: formMode, fieldName, fieldMessage });
44898
+ debug("forms", "invalid", { mode: formMode, fieldName: fieldName2, fieldMessage });
44859
44899
  queueMicrotask(() => {
44860
44900
  const form = formRef.current;
44861
44901
  if (!form) return;
@@ -44891,22 +44931,22 @@ var init_Form = __esm({
44891
44931
  };
44892
44932
  const renderField = React77__namespace.default.useCallback(
44893
44933
  (field) => {
44894
- const fieldName = field.name || field.field;
44895
- if (!fieldName) return null;
44896
- if (!isFieldVisible(fieldName)) {
44934
+ const fieldName2 = field.name || field.field;
44935
+ if (!fieldName2) return null;
44936
+ if (!isFieldVisible(fieldName2)) {
44897
44937
  return null;
44898
44938
  }
44899
44939
  const inputType = determineInputType(field);
44900
- const label = field.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, " $1");
44901
- const currentValue2 = formData[fieldName] ?? field.defaultValue ?? "";
44902
- return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", "data-field": fieldName, children: [
44940
+ const label = field.label || fieldName2.charAt(0).toUpperCase() + fieldName2.slice(1).replace(/([A-Z])/g, " $1");
44941
+ const currentValue2 = formData[fieldName2] ?? field.defaultValue ?? "";
44942
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", "data-field": fieldName2, children: [
44903
44943
  inputType !== "checkbox" && /* @__PURE__ */ jsxRuntime.jsxs(exports.Typography, { as: "label", variant: "label", weight: "bold", children: [
44904
44944
  label,
44905
44945
  field.required && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { as: "span", color: "error", className: "ml-1", children: "*" })
44906
44946
  ] }),
44907
- renderFieldInput(field, fieldName, inputType, currentValue2, label),
44947
+ renderFieldInput(field, fieldName2, inputType, currentValue2, label),
44908
44948
  field.hint && /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", children: field.hint })
44909
- ] }, fieldName);
44949
+ ] }, fieldName2);
44910
44950
  },
44911
44951
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
44912
44952
  );
@@ -44935,8 +44975,8 @@ var init_Form = __esm({
44935
44975
  }
44936
44976
  return field;
44937
44977
  }).map((field) => {
44938
- const fieldName = field.name || field.field;
44939
- const override = fieldOverrides?.find((o) => o.name === fieldName);
44978
+ const fieldName2 = field.name || field.field;
44979
+ const override = fieldOverrides?.find((o) => o.name === fieldName2);
44940
44980
  if (!override) return field;
44941
44981
  return {
44942
44982
  ...field,
@@ -44992,11 +45032,11 @@ var init_Form = __esm({
44992
45032
  ] }, section.id);
44993
45033
  }).filter(Boolean);
44994
45034
  }, [sections, isSectionVisible, collapsedSections, renderField, gap]);
44995
- function renderFieldInput(field, fieldName, inputType, currentValue2, label) {
45035
+ function renderFieldInput(field, fieldName2, inputType, currentValue2, label) {
44996
45036
  const commonProps = {
44997
- id: fieldName,
44998
- name: fieldName,
44999
- "data-field-name": fieldName,
45037
+ id: fieldName2,
45038
+ name: fieldName2,
45039
+ "data-field-name": fieldName2,
45000
45040
  required: field.required,
45001
45041
  disabled: isLoading,
45002
45042
  placeholder: field.placeholder,
@@ -45010,7 +45050,7 @@ var init_Form = __esm({
45010
45050
  ...commonProps,
45011
45051
  label: label + (field.required ? " *" : ""),
45012
45052
  checked: Boolean(currentValue2),
45013
- onChange: (e) => handleChange(fieldName, e.target.checked)
45053
+ onChange: (e) => handleChange(fieldName2, e.target.checked)
45014
45054
  }
45015
45055
  );
45016
45056
  case "textarea":
@@ -45019,7 +45059,7 @@ var init_Form = __esm({
45019
45059
  {
45020
45060
  ...commonProps,
45021
45061
  value: String(currentValue2),
45022
- onChange: (e) => handleChange(fieldName, e.target.value),
45062
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45023
45063
  minLength: field.min,
45024
45064
  maxLength: field.max
45025
45065
  }
@@ -45032,14 +45072,14 @@ var init_Form = __esm({
45032
45072
  ...commonProps,
45033
45073
  options,
45034
45074
  value: String(currentValue2),
45035
- onValueChange: (v) => handleChange(fieldName, v),
45075
+ onValueChange: (v) => handleChange(fieldName2, v),
45036
45076
  placeholder: field.placeholder || `Select ${label}...`
45037
45077
  }
45038
45078
  );
45039
45079
  }
45040
45080
  case "relation": {
45041
- const relationOptions = relationsData[fieldName] || [];
45042
- const relationLoading = relationsLoading[fieldName] || false;
45081
+ const relationOptions = relationsData[fieldName2] || [];
45082
+ const relationLoading = relationsLoading[fieldName2] || false;
45043
45083
  if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
45044
45084
  const selectedValues = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : [];
45045
45085
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -45051,7 +45091,7 @@ var init_Form = __esm({
45051
45091
  clearable: true,
45052
45092
  options: [...relationOptions],
45053
45093
  value: selectedValues,
45054
- onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
45094
+ onValueChange: (value) => handleChange(fieldName2, Array.isArray(value) ? value : [value]),
45055
45095
  placeholder: field.placeholder || `Select ${label}...`
45056
45096
  }
45057
45097
  );
@@ -45061,7 +45101,7 @@ var init_Form = __esm({
45061
45101
  {
45062
45102
  ...commonProps,
45063
45103
  value: currentValue2 ? String(currentValue2) : void 0,
45064
- onChange: (value) => handleChange(fieldName, value),
45104
+ onChange: (value) => handleChange(fieldName2, value),
45065
45105
  options: relationOptions,
45066
45106
  isLoading: relationLoading,
45067
45107
  placeholder: field.placeholder || `Select ${label}...`,
@@ -45078,7 +45118,7 @@ var init_Form = __esm({
45078
45118
  placeholder: field.placeholder,
45079
45119
  disabled: isLoading,
45080
45120
  value: arrayValue,
45081
- onChange: (next) => handleChange(fieldName, [...next])
45121
+ onChange: (next) => handleChange(fieldName2, [...next])
45082
45122
  }
45083
45123
  );
45084
45124
  }
@@ -45090,7 +45130,7 @@ var init_Form = __esm({
45090
45130
  type: "number",
45091
45131
  value: currentValue2 !== void 0 && currentValue2 !== "" ? String(currentValue2) : "",
45092
45132
  onChange: (e) => handleChange(
45093
- fieldName,
45133
+ fieldName2,
45094
45134
  e.target.value ? Number(e.target.value) : void 0
45095
45135
  ),
45096
45136
  min: field.min,
@@ -45107,7 +45147,7 @@ var init_Form = __esm({
45107
45147
  icon: LucideIcons2.DollarSign,
45108
45148
  value: currentValue2 !== void 0 && currentValue2 !== "" ? String(currentValue2) : "",
45109
45149
  onChange: (e) => handleChange(
45110
- fieldName,
45150
+ fieldName2,
45111
45151
  e.target.value ? Number(e.target.value) : void 0
45112
45152
  ),
45113
45153
  min: field.min,
@@ -45125,7 +45165,7 @@ var init_Form = __esm({
45125
45165
  const f3 = files[0];
45126
45166
  if (!f3) return;
45127
45167
  const reader = new FileReader();
45128
- reader.onload = () => handleChange(fieldName, {
45168
+ reader.onload = () => handleChange(fieldName2, {
45129
45169
  name: f3.name,
45130
45170
  mimeType: f3.type,
45131
45171
  sizeBytes: f3.size,
@@ -45142,7 +45182,7 @@ var init_Form = __esm({
45142
45182
  ...commonProps,
45143
45183
  type: "date",
45144
45184
  value: formatDateValue(currentValue2),
45145
- onChange: (e) => handleChange(fieldName, e.target.value)
45185
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45146
45186
  }
45147
45187
  );
45148
45188
  case "datetime-local":
@@ -45152,7 +45192,7 @@ var init_Form = __esm({
45152
45192
  ...commonProps,
45153
45193
  type: "datetime-local",
45154
45194
  value: formatDateTimeValue(currentValue2),
45155
- onChange: (e) => handleChange(fieldName, e.target.value)
45195
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45156
45196
  }
45157
45197
  );
45158
45198
  case "email":
@@ -45162,7 +45202,7 @@ var init_Form = __esm({
45162
45202
  ...commonProps,
45163
45203
  type: "email",
45164
45204
  value: String(currentValue2),
45165
- onChange: (e) => handleChange(fieldName, e.target.value),
45205
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45166
45206
  minLength: field.min,
45167
45207
  maxLength: field.max
45168
45208
  }
@@ -45174,7 +45214,7 @@ var init_Form = __esm({
45174
45214
  ...commonProps,
45175
45215
  type: "url",
45176
45216
  value: String(currentValue2),
45177
- onChange: (e) => handleChange(fieldName, e.target.value),
45217
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45178
45218
  minLength: field.min,
45179
45219
  maxLength: field.max
45180
45220
  }
@@ -45186,7 +45226,7 @@ var init_Form = __esm({
45186
45226
  ...commonProps,
45187
45227
  type: "password",
45188
45228
  value: String(currentValue2),
45189
- onChange: (e) => handleChange(fieldName, e.target.value),
45229
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45190
45230
  minLength: field.min,
45191
45231
  maxLength: field.max
45192
45232
  }
@@ -45199,7 +45239,7 @@ var init_Form = __esm({
45199
45239
  ...commonProps,
45200
45240
  type: "text",
45201
45241
  value: String(currentValue2),
45202
- onChange: (e) => handleChange(fieldName, e.target.value),
45242
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45203
45243
  minLength: field.min,
45204
45244
  maxLength: field.max
45205
45245
  }
@@ -45556,7 +45596,7 @@ function entityFieldsFromListItem(item) {
45556
45596
  }
45557
45597
  return result;
45558
45598
  }
45559
- function getStatusStyle(fieldName, value) {
45599
+ function getStatusStyle(fieldName2, value) {
45560
45600
  const val = String(value).toLowerCase();
45561
45601
  if (val.includes("complete") || val.includes("done"))
45562
45602
  return STATUS_STYLES.complete;
@@ -45572,12 +45612,12 @@ function getStatusStyle(fieldName, value) {
45572
45612
  if (val.includes("low")) return STATUS_STYLES.low;
45573
45613
  return STATUS_STYLES.default;
45574
45614
  }
45575
- function formatValue3(value, fieldName) {
45615
+ function formatValue3(value, fieldName2) {
45576
45616
  if (typeof value === "number") {
45577
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
45617
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
45578
45618
  return `${value}%`;
45579
45619
  }
45580
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
45620
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
45581
45621
  return new Intl.NumberFormat("en-US", {
45582
45622
  style: "currency",
45583
45623
  currency: "USD",
@@ -45600,8 +45640,8 @@ function formatValue3(value, fieldName) {
45600
45640
  }
45601
45641
  return String(value);
45602
45642
  }
45603
- function formatFieldLabel2(fieldName) {
45604
- return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
45643
+ function formatFieldLabel2(fieldName2) {
45644
+ return humanizeFieldName(fieldName2).replace(/\sId$/, "").trim();
45605
45645
  }
45606
45646
  var STATUS_STYLES, StatusBadge, ProgressIndicator; exports.List = void 0;
45607
45647
  var init_List = __esm({
@@ -45669,9 +45709,9 @@ var init_List = __esm({
45669
45709
  };
45670
45710
  StatusBadge = ({
45671
45711
  value,
45672
- fieldName
45712
+ fieldName: fieldName2
45673
45713
  }) => {
45674
- const style = getStatusStyle(fieldName, value);
45714
+ const style = getStatusStyle(fieldName2, value);
45675
45715
  return /* @__PURE__ */ jsxRuntime.jsxs(
45676
45716
  exports.Typography,
45677
45717
  {
@@ -48584,8 +48624,8 @@ var init_StatCard = __esm({
48584
48624
  return items.length;
48585
48625
  }
48586
48626
  if (field.includes(":")) {
48587
- const [fieldName, fieldValue] = field.split(":");
48588
- return items.filter((item) => item[fieldName] === fieldValue).length;
48627
+ const [fieldName2, fieldValue] = field.split(":");
48628
+ return items.filter((item) => item[fieldName2] === fieldValue).length;
48589
48629
  }
48590
48630
  const fieldExistsOnItems = items.some((item) => field in item);
48591
48631
  if (fieldExistsOnItems) {
@@ -50105,6 +50145,8 @@ var init_component_registry_generated = __esm({
50105
50145
  init_DocSearch();
50106
50146
  init_DocSidebar();
50107
50147
  init_DocTOC();
50148
+ init_DocumentDetails();
50149
+ init_DocumentPanel();
50108
50150
  init_DocumentViewer();
50109
50151
  init_DrawFxLayer();
50110
50152
  init_DrawGroup();
@@ -50214,7 +50256,7 @@ var init_component_registry_generated = __esm({
50214
50256
  init_RelationSelect();
50215
50257
  init_RepeatableFormSection();
50216
50258
  init_ReplyTree();
50217
- init_RichBlockEditor();
50259
+ init_RichTextEditor();
50218
50260
  init_RuntimeDebugger2();
50219
50261
  init_ScaledDiagram();
50220
50262
  init_ScoreDisplay();
@@ -50377,6 +50419,8 @@ var init_component_registry_generated = __esm({
50377
50419
  "DocSearch": DocSearch,
50378
50420
  "DocSidebar": exports.DocSidebar,
50379
50421
  "DocTOC": exports.DocTOC,
50422
+ "DocumentDetails": DocumentDetails,
50423
+ "DocumentPanel": DocumentPanel,
50380
50424
  "DocumentViewer": exports.DocumentViewer,
50381
50425
  "DrawFxLayer": DrawFxLayer,
50382
50426
  "DrawGroup": DrawGroup,
@@ -50488,7 +50532,7 @@ var init_component_registry_generated = __esm({
50488
50532
  "RelationSelect": exports.RelationSelect,
50489
50533
  "RepeatableFormSection": exports.RepeatableFormSection,
50490
50534
  "ReplyTree": exports.ReplyTree,
50491
- "RichBlockEditor": exports.RichBlockEditor,
50535
+ "RichTextEditor": exports.RichTextEditor,
50492
50536
  "RuntimeDebugger": RuntimeDebugger,
50493
50537
  "ScaledDiagram": exports.ScaledDiagram,
50494
50538
  "ScoreDisplay": ScoreDisplay,
@@ -50642,10 +50686,10 @@ function enrichFormFields(fields, entityDef) {
50642
50686
  }
50643
50687
  if (field && typeof field === "object" && !Array.isArray(field) && !React77__namespace.default.isValidElement(field) && !(field instanceof Date)) {
50644
50688
  const obj = field;
50645
- const fieldName = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
50646
- if (!fieldName) return field;
50689
+ const fieldName2 = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
50690
+ if (!fieldName2) return field;
50647
50691
  if (obj.type || obj.inputType) return field;
50648
- const entityField = fieldMap.get(fieldName);
50692
+ const entityField = fieldMap.get(fieldName2);
50649
50693
  if (!entityField) return field;
50650
50694
  const enriched = { ...obj, type: entityField.type };
50651
50695
  if (entityField.required && !("required" in obj)) {
@@ -50695,9 +50739,9 @@ function enrichDetailFields(fields, entityDef) {
50695
50739
  }
50696
50740
  if (field && typeof field === "object" && !Array.isArray(field) && !React77__namespace.default.isValidElement(field) && !(field instanceof Date)) {
50697
50741
  const obj = field;
50698
- const fieldName = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
50699
- if (!fieldName || obj.type) return field;
50700
- const meta = metaFor(fieldName);
50742
+ const fieldName2 = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
50743
+ if (!fieldName2 || obj.type) return field;
50744
+ const meta = metaFor(fieldName2);
50701
50745
  return meta ? { ...obj, ...meta } : field;
50702
50746
  }
50703
50747
  return field;
@@ -51536,9 +51580,9 @@ function UISlotRenderer({
51536
51580
  "ui-slot-renderer relative min-h-full",
51537
51581
  className
51538
51582
  ), children: [
51539
- /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "flex min-h-full", children: [
51540
- /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 shrink-0" }),
51541
- /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-h-[200px]" })
51583
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "flex min-h-full flex-col lg:flex-row", children: [
51584
+ /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 lg:shrink-0" }),
51585
+ /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-w-0 min-h-[200px]" })
51542
51586
  ] }),
51543
51587
  /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "modal", portal: true }),
51544
51588
  /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "drawer", portal: true }),
@@ -53526,54 +53570,6 @@ var en_default = {
53526
53570
  "template.faq": "Frequently Asked Questions",
53527
53571
  "template.ourTeam": "Our Team",
53528
53572
  "template.caseStudies": "Case Studies",
53529
- "richBlockEditor.toolbar.text": "Text",
53530
- "richBlockEditor.toolbar.h1": "H1",
53531
- "richBlockEditor.toolbar.h2": "H2",
53532
- "richBlockEditor.toolbar.h3": "H3",
53533
- "richBlockEditor.toolbar.bulletList": "Bullet list",
53534
- "richBlockEditor.toolbar.numbered": "Numbered",
53535
- "richBlockEditor.toolbar.quote": "Quote",
53536
- "richBlockEditor.toolbar.code": "Code",
53537
- "richBlockEditor.toolbar.divider": "Divider",
53538
- "richBlockEditor.toolbar.image": "Image",
53539
- "richBlockEditor.blockType.paragraph": "Text",
53540
- "richBlockEditor.blockType.heading1": "Heading 1",
53541
- "richBlockEditor.blockType.heading2": "Heading 2",
53542
- "richBlockEditor.blockType.heading3": "Heading 3",
53543
- "richBlockEditor.blockType.bulletList": "Bullet list",
53544
- "richBlockEditor.blockType.numberedList": "Numbered list",
53545
- "richBlockEditor.blockType.quote": "Quote",
53546
- "richBlockEditor.blockType.code": "Code",
53547
- "richBlockEditor.blockType.divider": "Divider",
53548
- "richBlockEditor.blockType.image": "Image",
53549
- "richBlockEditor.blockActions": "Block actions",
53550
- "richBlockEditor.duplicate": "Duplicate",
53551
- "richBlockEditor.turnInto": "Turn into",
53552
- "richBlockEditor.placeholder.heading1": "Heading 1",
53553
- "richBlockEditor.placeholder.heading2": "Heading 2",
53554
- "richBlockEditor.placeholder.heading3": "Heading 3",
53555
- "richBlockEditor.placeholder.quote": "Quote",
53556
- "richBlockEditor.placeholder.code": "Enter code",
53557
- "richBlockEditor.placeholder.paragraph": "Start writing...",
53558
- "richBlockEditor.placeholder.listItem": "List item",
53559
- "richBlockEditor.placeholder.caption": "Caption (optional)",
53560
- "richBlockEditor.aria.heading1Block": "Heading 1 block",
53561
- "richBlockEditor.aria.heading2Block": "Heading 2 block",
53562
- "richBlockEditor.aria.heading3Block": "Heading 3 block",
53563
- "richBlockEditor.aria.quoteBlock": "Quote block",
53564
- "richBlockEditor.aria.codeBlock": "Code block",
53565
- "richBlockEditor.aria.codeLanguage": "Code language",
53566
- "richBlockEditor.aria.imageUrl": "Image URL",
53567
- "richBlockEditor.aria.imageCaption": "Image caption",
53568
- "richBlockEditor.aria.listItem": "List item",
53569
- "richBlockEditor.aria.removeListItem": "Remove list item",
53570
- "richBlockEditor.aria.paragraphBlock": "Paragraph block",
53571
- "richBlockEditor.embeddedImage": "Embedded image",
53572
- "richBlockEditor.noImageUrl": "No image URL set",
53573
- "richBlockEditor.addItem": "Add item",
53574
- "richBlockEditor.insertParagraphBelow": "Insert paragraph below",
53575
- "richBlockEditor.editorToolbar": "Block editor toolbar",
53576
- "richBlockEditor.insertEntry": "Insert {{label}}",
53577
53573
  "versionDiff.compare": "Compare",
53578
53574
  "versionDiff.to": "to",
53579
53575
  "versionDiff.beforeRevision": "Before revision",
@@ -53921,7 +53917,31 @@ var en_default = {
53921
53917
  "td.survivedAllWaves": "Survived all waves!",
53922
53918
  "td.victory": "Victory!",
53923
53919
  "td.wave": "Wave",
53924
- "td.waveInProgress": "Wave in progress\u2026"
53920
+ "td.waveInProgress": "Wave in progress\u2026",
53921
+ "documentDetails.title": "Details",
53922
+ "documentPanel.untitled": "Untitled",
53923
+ "documentPanel.edit": "Edit",
53924
+ "documentPanel.done": "Done",
53925
+ "documentPanel.autosaveHint": "Saves as you type",
53926
+ "documentPanel.placeholder": "Start writing\u2026",
53927
+ "richTextEditor.editorToolbar": "Formatting toolbar",
53928
+ "richTextEditor.editorSurface": "Rich text editor",
53929
+ "richTextEditor.bold": "Bold",
53930
+ "richTextEditor.italic": "Italic",
53931
+ "richTextEditor.underline": "Underline",
53932
+ "richTextEditor.strikethrough": "Strikethrough",
53933
+ "richTextEditor.heading1": "Heading 1",
53934
+ "richTextEditor.heading2": "Heading 2",
53935
+ "richTextEditor.heading3": "Heading 3",
53936
+ "richTextEditor.bulletList": "Bullet list",
53937
+ "richTextEditor.numberedList": "Numbered list",
53938
+ "richTextEditor.quote": "Quote",
53939
+ "richTextEditor.code": "Code",
53940
+ "richTextEditor.link": "Add link",
53941
+ "richTextEditor.removeLink": "Remove link",
53942
+ "richTextEditor.divider": "Divider",
53943
+ "richTextEditor.linkPrompt": "Link address",
53944
+ "richTextEditor.placeholder": "Start writing\u2026"
53925
53945
  };
53926
53946
 
53927
53947
  // hooks/useTranslate.ts
@@ -53942,7 +53962,7 @@ var I18nContext = React77.createContext({
53942
53962
  });
53943
53963
  I18nContext.displayName = "I18nContext";
53944
53964
  var I18nProvider = I18nContext.Provider;
53945
- function useTranslate114() {
53965
+ function useTranslate116() {
53946
53966
  return React77.useContext(I18nContext);
53947
53967
  }
53948
53968
  function createTranslate(messages) {
@@ -54262,6 +54282,8 @@ exports.DayCell = DayCell;
54262
54282
  exports.DialogueBubble = DialogueBubble;
54263
54283
  exports.DocPagination = DocPagination;
54264
54284
  exports.DocSearch = DocSearch;
54285
+ exports.DocumentDetails = DocumentDetails;
54286
+ exports.DocumentPanel = DocumentPanel;
54265
54287
  exports.DomStateMachineVisualizer = exports.StateMachineView;
54266
54288
  exports.EditorCheckbox = EditorCheckbox;
54267
54289
  exports.EditorSelect = EditorSelect;
@@ -54342,6 +54364,7 @@ exports.resolveFrame = resolveFrame;
54342
54364
  exports.resolveSheetDirection = resolveSheetDirection;
54343
54365
  exports.rows = rows;
54344
54366
  exports.runTickFrame = runTickFrame;
54367
+ exports.sanitizeRichHtml = sanitizeRichHtml;
54345
54368
  exports.screenToIso = screenToIso;
54346
54369
  exports.str = str;
54347
54370
  exports.tickAnimationState = tickAnimationState;
@@ -54390,7 +54413,7 @@ exports.useSharedEntityStoreContext = useSharedEntityStoreContext;
54390
54413
  exports.useSwipeGesture = useSwipeGesture;
54391
54414
  exports.useTapReveal = useTapReveal;
54392
54415
  exports.useTraitListens = useTraitListens;
54393
- exports.useTranslate = useTranslate114;
54416
+ exports.useTranslate = useTranslate116;
54394
54417
  exports.useUIEvents = useUIEvents;
54395
54418
  exports.useUISlotManager = useUISlotManager;
54396
54419
  exports.useUnitSpriteAtlas = useUnitSpriteAtlas;