@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.
@@ -25903,8 +25903,9 @@ function DataGrid({
25903
25903
  const titleField = fieldDefs.find((f3) => f3.variant === "h3" || f3.variant === "h4") ?? fieldDefs[0];
25904
25904
  const badgeFields = fieldDefs.filter((f3) => f3.variant === "badge" && f3 !== titleField);
25905
25905
  const bodyFields = fieldDefs.filter((f3) => f3 !== titleField && !badgeFields.includes(f3));
25906
- const primaryActions = actionDefs.filter((a) => a.variant !== "danger");
25907
- const dangerActions = actionDefs.filter((a) => a.variant === "danger");
25906
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
25907
+ const inlineCardActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
25908
+ const menuCardActions = actionDefs.filter((a) => !inlineCardActions.includes(a));
25908
25909
  const fireAction = (action, itemData) => {
25909
25910
  if (action.navigatesTo) {
25910
25911
  const url = action.navigatesTo.replace(
@@ -25924,6 +25925,9 @@ function DataGrid({
25924
25925
  e.stopPropagation();
25925
25926
  fireAction(action, itemData);
25926
25927
  };
25928
+ const cardClickAction = actionDefs.find((a) => a.variant !== "danger");
25929
+ const handleCardClick = cardClickAction ? (itemData) => () => fireAction(cardClickAction, itemData) : void 0;
25930
+ const stopCardClick = handleCardClick ? (e) => e.stopPropagation() : void 0;
25927
25931
  const hasRenderProp = typeof children === "function";
25928
25932
  React85.useEffect(() => {
25929
25933
  if (data.length > 0 && !hasRenderProp && fieldDefs.length === 0) {
@@ -25995,55 +25999,40 @@ function DataGrid({
25995
25999
  {
25996
26000
  "data-entity-row": true,
25997
26001
  "data-entity-id": id,
25998
- className: cn("relative group/rowactions", isSelected && "ring-2 ring-primary rounded-lg"),
26002
+ onClick: handleCardClick?.(itemData),
26003
+ className: cn("relative group/rowactions", handleCardClick && "cursor-pointer", isSelected && "ring-2 ring-primary rounded-lg"),
25999
26004
  children: [
26000
26005
  children(itemData, index),
26001
- actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
26002
- /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: [
26003
- (maxInlineActions != null ? actionDefs.slice(0, maxInlineActions) : actionDefs).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
26004
- Button,
26005
- {
26006
- variant: action.variant === "primary" ? "primary" : "ghost",
26007
- size: "sm",
26008
- onClick: handleActionClick(action, itemData),
26009
- "data-testid": `action-${action.event}`,
26010
- "data-row-id": String(itemData.id),
26011
- className: action.variant === "danger" ? "text-error hover:text-error hover:bg-error/10" : void 0,
26012
- children: [
26013
- action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
26014
- action.label
26015
- ]
26016
- },
26017
- idx
26018
- )),
26019
- maxInlineActions != null && actionDefs.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
26020
- Menu,
26021
- {
26022
- position: "bottom-end",
26023
- trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26024
- items: actionDefs.slice(maxInlineActions).map((action) => ({
26025
- label: action.label,
26026
- icon: action.icon,
26027
- event: action.event,
26028
- onClick: () => fireAction(action, itemData)
26029
- }))
26030
- }
26031
- )
26032
- ] }),
26033
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "hidden [@media(pointer:coarse)]:block rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
26006
+ actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Box, { onClick: stopCardClick, className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: [
26007
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
26008
+ Button,
26009
+ {
26010
+ variant: "primary",
26011
+ size: "sm",
26012
+ onClick: handleActionClick(action, itemData),
26013
+ "data-testid": `action-${action.event}`,
26014
+ "data-row-id": String(itemData.id),
26015
+ children: [
26016
+ action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
26017
+ action.label
26018
+ ]
26019
+ },
26020
+ idx
26021
+ )),
26022
+ menuCardActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
26034
26023
  Menu,
26035
26024
  {
26036
26025
  position: "bottom-end",
26037
26026
  trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26038
- items: actionDefs.map((action) => ({
26027
+ items: menuCardActions.map((action) => ({
26039
26028
  label: action.label,
26040
26029
  icon: action.icon,
26041
- event: action.event,
26030
+ variant: action.variant === "danger" ? "danger" : "default",
26042
26031
  onClick: () => fireAction(action, itemData)
26043
26032
  }))
26044
26033
  }
26045
- ) })
26046
- ] })
26034
+ )
26035
+ ] }) })
26047
26036
  ]
26048
26037
  },
26049
26038
  id
@@ -26057,12 +26046,14 @@ function DataGrid({
26057
26046
  {
26058
26047
  "data-entity-row": true,
26059
26048
  "data-entity-id": id,
26049
+ onClick: handleCardClick?.(itemData),
26060
26050
  className: cn(
26061
26051
  "bg-card rounded-lg",
26062
26052
  "border border-border",
26063
26053
  "shadow-elevation-card hover:shadow-elevation-dialog",
26064
26054
  "hover:border-primary transition-all",
26065
26055
  "flex flex-col",
26056
+ handleCardClick && "cursor-pointer",
26066
26057
  isSelected && "ring-2 ring-primary border-primary"
26067
26058
  ),
26068
26059
  children: [
@@ -26112,53 +26103,35 @@ function DataGrid({
26112
26103
  ] }, field.name);
26113
26104
  }) })
26114
26105
  ] }),
26115
- (primaryActions.length > 0 || dangerActions.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
26116
- (maxInlineActions != null ? primaryActions.slice(0, maxInlineActions) : primaryActions).map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
26106
+ actionDefs.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", onClick: stopCardClick, className: "flex-shrink-0", children: [
26107
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
26117
26108
  Button,
26118
26109
  {
26119
- variant: action.variant === "primary" ? "primary" : "ghost",
26110
+ variant: "primary",
26120
26111
  size: "sm",
26121
26112
  onClick: handleActionClick(action, itemData),
26122
26113
  "data-testid": `action-${action.event}`,
26123
26114
  "data-row-id": String(itemData.id),
26124
26115
  "aria-label": action.label,
26125
26116
  title: action.label,
26126
- className: cn(
26127
- action.variant === "primary" ? void 0 : "text-muted-foreground hover:text-foreground",
26128
- action.icon && "px-2"
26129
- ),
26117
+ className: cn(action.icon && "px-2"),
26130
26118
  children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
26131
26119
  },
26132
26120
  idx
26133
26121
  )),
26134
- maxInlineActions != null && primaryActions.length > maxInlineActions && /* @__PURE__ */ jsxRuntime.jsx(
26122
+ menuCardActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
26135
26123
  Menu,
26136
26124
  {
26137
26125
  position: "bottom-end",
26138
26126
  trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26139
- items: primaryActions.slice(maxInlineActions).map((action) => ({
26127
+ items: menuCardActions.map((action) => ({
26140
26128
  label: action.label,
26141
26129
  icon: action.icon,
26142
- event: action.event,
26130
+ variant: action.variant === "danger" ? "danger" : "default",
26143
26131
  onClick: () => fireAction(action, itemData)
26144
26132
  }))
26145
26133
  }
26146
- ),
26147
- dangerActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsx(
26148
- Button,
26149
- {
26150
- variant: "ghost",
26151
- size: "sm",
26152
- onClick: handleActionClick(action, itemData),
26153
- "data-testid": `action-${action.event}`,
26154
- "data-row-id": String(itemData.id),
26155
- "aria-label": action.label,
26156
- title: action.label,
26157
- className: "text-error hover:text-error hover:bg-error/10 px-2",
26158
- children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
26159
- },
26160
- `danger-${idx}`
26161
- ))
26134
+ )
26162
26135
  ] })
26163
26136
  ] }) }),
26164
26137
  bodyFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-4 pt-2 pb-4 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
@@ -26179,8 +26152,8 @@ function DataGrid({
26179
26152
  }
26180
26153
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
26181
26154
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
26182
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26183
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", children: formatValue(value, field.format) })
26155
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26156
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
26184
26157
  ] }, field.name);
26185
26158
  }) })
26186
26159
  ] }) })
@@ -26400,54 +26373,59 @@ function DataList({
26400
26373
  if (!itemActions || itemActions.length === 0) return null;
26401
26374
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
26402
26375
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
26403
- return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
26404
- inline.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
26405
- Button,
26406
- {
26407
- variant: action.variant ?? "ghost",
26408
- size: "sm",
26409
- onClick: handleActionClick(action, itemData),
26410
- "data-testid": `action-${action.event}`,
26411
- "data-row-id": String(itemData.id),
26412
- className: cn(
26413
- action.variant === "danger" && "text-error hover:bg-error/10",
26414
- // Must sit on the Button itself: the variant's own text colour
26415
- // beats an inherited one from the row wrapper.
26416
- onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
26417
- ),
26418
- children: [
26419
- action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
26420
- action.label
26421
- ]
26422
- },
26423
- idx
26424
- )),
26425
- overflow.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
26426
- Menu,
26427
- {
26428
- position: "bottom-end",
26429
- trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26430
- items: overflow.map((action) => ({
26431
- label: action.label,
26432
- icon: action.icon,
26433
- event: action.event,
26434
- variant: action.variant === "danger" ? "danger" : "default",
26435
- onClick: () => eventBus.emit(`UI:${action.event}`, {
26436
- id: itemData.id,
26437
- row: itemData
26438
- })
26439
- }))
26440
- }
26441
- )
26442
- ] });
26376
+ return (
26377
+ // stopPropagation at the cluster: the "⋯" trigger opens the overflow
26378
+ // menu without also firing the row's default click (inline buttons
26379
+ // already stop it in handleActionClick; the Menu panel is portaled).
26380
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, className: "flex-shrink-0", children: [
26381
+ inline.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
26382
+ Button,
26383
+ {
26384
+ variant: action.variant ?? "ghost",
26385
+ size: "sm",
26386
+ onClick: handleActionClick(action, itemData),
26387
+ "data-testid": `action-${action.event}`,
26388
+ "data-row-id": String(itemData.id),
26389
+ className: cn(
26390
+ action.variant === "danger" && "text-error hover:bg-error/10",
26391
+ // Must sit on the Button itself: the variant's own text colour
26392
+ // beats an inherited one from the row wrapper.
26393
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
26394
+ ),
26395
+ children: [
26396
+ action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
26397
+ action.label
26398
+ ]
26399
+ },
26400
+ idx
26401
+ )),
26402
+ overflow.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
26403
+ Menu,
26404
+ {
26405
+ position: "bottom-end",
26406
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26407
+ items: overflow.map((action) => ({
26408
+ label: action.label,
26409
+ icon: action.icon,
26410
+ variant: action.variant === "danger" ? "danger" : "default",
26411
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
26412
+ id: itemData.id,
26413
+ row: itemData
26414
+ })
26415
+ }))
26416
+ }
26417
+ )
26418
+ ] })
26419
+ );
26443
26420
  };
26421
+ const rowClickEvent = itemClickEvent || itemActions?.find((a) => a.variant !== "danger")?.event;
26444
26422
  const handleRowClick = (itemData) => () => {
26445
- if (!itemClickEvent) return;
26423
+ if (!rowClickEvent) return;
26446
26424
  const payload = {
26447
26425
  id: itemData.id,
26448
26426
  row: itemData
26449
26427
  };
26450
- eventBus.emit(`UI:${itemClickEvent}`, payload);
26428
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
26451
26429
  };
26452
26430
  if (isLoading) {
26453
26431
  return /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
@@ -26495,10 +26473,10 @@ function DataList({
26495
26473
  {
26496
26474
  "data-entity-row": true,
26497
26475
  "data-entity-id": id,
26498
- onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
26476
+ onClick: rowClickEvent ? handleRowClick(itemData) : void 0,
26499
26477
  className: cn(
26500
26478
  "flex px-4 group/rowactions",
26501
- itemClickEvent && "cursor-pointer",
26479
+ rowClickEvent && "cursor-pointer",
26502
26480
  isSent ? "justify-end" : "justify-start"
26503
26481
  ),
26504
26482
  children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -26574,7 +26552,7 @@ function DataList({
26574
26552
  const id2 = itemData.id || String(index);
26575
26553
  const actions = renderItemActions(itemData);
26576
26554
  return wrapDnd(
26577
- /* @__PURE__ */ jsxRuntime.jsxs(Box, { "data-entity-row": true, "data-entity-id": id2, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", itemClickEvent && "cursor-pointer"), children: [
26555
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { "data-entity-row": true, "data-entity-id": id2, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", rowClickEvent && "cursor-pointer"), children: [
26578
26556
  children(itemData, index),
26579
26557
  actions && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
26580
26558
  /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: actions }),
@@ -26586,7 +26564,6 @@ function DataList({
26586
26564
  items: (itemActions ?? []).map((action) => ({
26587
26565
  label: action.label,
26588
26566
  icon: action.icon,
26589
- event: action.event,
26590
26567
  variant: action.variant === "danger" ? "danger" : "default",
26591
26568
  onClick: () => eventBus.emit(`UI:${action.event}`, {
26592
26569
  id: itemData.id,
@@ -26603,12 +26580,16 @@ function DataList({
26603
26580
  const id = itemData.id || String(index);
26604
26581
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
26605
26582
  return wrapDnd(
26606
- /* @__PURE__ */ jsxRuntime.jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn(itemClickEvent && "cursor-pointer"), children: [
26583
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
26607
26584
  /* @__PURE__ */ jsxRuntime.jsxs(
26608
26585
  Box,
26609
26586
  {
26610
26587
  className: cn(
26611
- "group flex items-center gap-4 transition-all duration-fast",
26588
+ // items-start, not items-center: a multi-line row (title + meta +
26589
+ // progress) centred its action cluster in the vertical middle, so
26590
+ // the buttons floated between the meta fields instead of anchoring
26591
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
26592
+ "group flex items-start gap-4 transition-all duration-fast",
26612
26593
  isCompact ? "px-4 py-2" : "px-6 py-4",
26613
26594
  "hover:bg-muted/80",
26614
26595
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -26639,11 +26620,19 @@ function DataList({
26639
26620
  if (value === void 0 || value === null || value === "") return null;
26640
26621
  return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
26641
26622
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
26642
- /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "caption", color: "secondary", children: [
26643
- field.label ?? fieldLabel3(field.name),
26644
- ":"
26645
- ] }),
26646
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
26623
+ /* @__PURE__ */ jsxRuntime.jsxs(
26624
+ Typography,
26625
+ {
26626
+ variant: "caption",
26627
+ color: "secondary",
26628
+ className: cn(field.format !== "boolean" && "sr-only"),
26629
+ children: [
26630
+ field.label ?? fieldLabel3(field.name),
26631
+ ":"
26632
+ ]
26633
+ }
26634
+ ),
26635
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
26647
26636
  ] }, field.name);
26648
26637
  }) }),
26649
26638
  progressFields.map((field) => {
@@ -26662,7 +26651,7 @@ function DataList({
26662
26651
  ]
26663
26652
  }
26664
26653
  ),
26665
- isCard && !isLast && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-6 border-b border-border/40" })
26654
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
26666
26655
  ] }, id)
26667
26656
  );
26668
26657
  };
@@ -26672,7 +26661,13 @@ function DataList({
26672
26661
  {
26673
26662
  className: cn(
26674
26663
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
26675
- !isCard && gapClass,
26664
+ // `gap-*` is inert on a block container, and Box only emits a display
26665
+ // class when its `display` prop is set — so every non-card list had
26666
+ // been asking for a gap that CSS silently dropped. flex-col makes it
26667
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
26668
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
26669
+ !isCard && "flex flex-col",
26670
+ !isCard && !isCompact && gapClass,
26676
26671
  listLookStyles[look],
26677
26672
  className
26678
26673
  ),
@@ -31797,7 +31792,7 @@ var init_MapView = __esm({
31797
31792
  shadowSize: [41, 41]
31798
31793
  });
31799
31794
  L.Marker.prototype.options.icon = defaultIcon;
31800
- const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback90, useState: useState92 } = React85__namespace.default;
31795
+ const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback90, useState: useState94 } = React85__namespace.default;
31801
31796
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31802
31797
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31803
31798
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -31842,7 +31837,7 @@ var init_MapView = __esm({
31842
31837
  showAttribution = true
31843
31838
  }) {
31844
31839
  const eventBus = useEventBus2();
31845
- const [clickedPosition, setClickedPosition] = useState92(null);
31840
+ const [clickedPosition, setClickedPosition] = useState94(null);
31846
31841
  const handleMapClick = useCallback90((lat, lng) => {
31847
31842
  if (showClickedPin) {
31848
31843
  setClickedPosition({ lat, lng });
@@ -36066,741 +36061,775 @@ var init_PositionedCanvas = __esm({
36066
36061
  PositionedCanvas.displayName = "PositionedCanvas";
36067
36062
  }
36068
36063
  });
36069
- function nextBlockId(prefix = "blk") {
36070
- _idSeq += 1;
36071
- const random = Math.random().toString(36).slice(2, 8);
36072
- return `${prefix}-${Date.now().toString(36)}-${_idSeq}-${random}`;
36064
+ function safeUrl(raw, kinds) {
36065
+ const url = raw.trim();
36066
+ if (url.startsWith("#") || url.startsWith("/")) return url;
36067
+ const lower = url.toLowerCase();
36068
+ for (const kind of kinds) {
36069
+ if (lower.startsWith(kind)) return url;
36070
+ }
36071
+ return null;
36073
36072
  }
36074
- function normalizeBlocks(raw) {
36075
- if (!Array.isArray(raw) || raw.length === 0) return [createBlock("paragraph")];
36076
- return raw.map((row) => {
36077
- const entity = row;
36078
- const rawType = entity.type;
36079
- const type = typeof rawType === "string" && BLOCK_TYPES.has(rawType) ? rawType : "paragraph";
36080
- const id = typeof entity.id === "string" && entity.id ? entity.id : nextBlockId(type);
36081
- return { ...entity, id, type };
36082
- });
36073
+ function sanitizeNode(el) {
36074
+ const children = Array.from(el.children);
36075
+ for (const child of children) {
36076
+ const tag = child.tagName.toLowerCase();
36077
+ if (DROP_TAGS.has(tag)) {
36078
+ child.remove();
36079
+ continue;
36080
+ }
36081
+ const allowed = ALLOWED_ATTRS[tag];
36082
+ if (!allowed) {
36083
+ sanitizeNode(child);
36084
+ child.replaceWith(...Array.from(child.childNodes));
36085
+ continue;
36086
+ }
36087
+ for (const attr of Array.from(child.attributes)) {
36088
+ const name = attr.name.toLowerCase();
36089
+ if (!allowed.includes(name)) {
36090
+ child.removeAttribute(attr.name);
36091
+ continue;
36092
+ }
36093
+ if (name === "href") {
36094
+ const url = safeUrl(attr.value, ["http://", "https://", "mailto:"]);
36095
+ if (url === null) child.removeAttribute(attr.name);
36096
+ else child.setAttribute("href", url);
36097
+ }
36098
+ if (name === "src") {
36099
+ const url = safeUrl(attr.value, ["http://", "https://", "data:image/"]);
36100
+ if (url === null) child.remove();
36101
+ else child.setAttribute("src", url);
36102
+ }
36103
+ }
36104
+ sanitizeNode(child);
36105
+ }
36083
36106
  }
36084
- function createBlock(type) {
36085
- switch (type) {
36086
- case "bullet-list":
36087
- case "numbered-list":
36088
- return {
36089
- id: nextBlockId(type),
36090
- type,
36091
- children: [
36092
- { id: nextBlockId("li"), type: "paragraph", content: "" }
36093
- ]
36094
- };
36095
- case "image":
36096
- return {
36097
- id: nextBlockId(type),
36098
- type,
36099
- content: "",
36100
- metadata: { url: "", caption: "" }
36101
- };
36102
- case "code":
36103
- return {
36104
- id: nextBlockId(type),
36105
- type,
36106
- content: "",
36107
- metadata: { language: "plaintext" }
36108
- };
36109
- case "divider":
36110
- return { id: nextBlockId(type), type };
36111
- default:
36112
- return { id: nextBlockId(type), type, content: "" };
36107
+ function sanitizeRichHtml(html) {
36108
+ if (!html || typeof html !== "string") return "";
36109
+ if (typeof window === "undefined" || typeof DOMParser === "undefined") return "";
36110
+ const doc = new DOMParser().parseFromString(html, "text/html");
36111
+ sanitizeNode(doc.body);
36112
+ if (!doc.body.querySelector("img, hr") && (doc.body.textContent ?? "").trim().length === 0) {
36113
+ return "";
36113
36114
  }
36115
+ return doc.body.innerHTML.replace(/​/g, "");
36114
36116
  }
36115
- function replaceBlock(blocks, id, updater) {
36116
- return blocks.map((block) => block.id === id ? updater(block) : block);
36117
+ function htmlIsEmpty(el) {
36118
+ if (el.querySelector("img, hr")) return false;
36119
+ return (el.textContent ?? "").trim().length === 0;
36117
36120
  }
36118
- function removeBlock(blocks, id) {
36119
- return blocks.filter((block) => block.id !== id);
36121
+ function closestBlock(node, root) {
36122
+ let cur = node;
36123
+ while (cur && cur !== root) {
36124
+ if (cur instanceof Element && BLOCK_TAGS.has(cur.tagName)) return cur;
36125
+ cur = cur.parentNode;
36126
+ }
36127
+ return null;
36120
36128
  }
36121
- function duplicateBlock(block) {
36129
+ function RichTextStyles() {
36130
+ return /* @__PURE__ */ jsxRuntime.jsx("style", { children: RICH_TEXT_CSS });
36131
+ }
36132
+ function readToolbarState(root) {
36133
+ const sel = window.getSelection();
36134
+ if (!sel || sel.rangeCount === 0) return IDLE_TOOLBAR;
36135
+ const anchor = sel.anchorNode;
36136
+ if (!anchor || !root.contains(anchor)) return IDLE_TOOLBAR;
36137
+ let block = null;
36138
+ let link = false;
36139
+ let node = anchor;
36140
+ while (node && node !== root) {
36141
+ if (node instanceof Element) {
36142
+ const tag = node.tagName.toLowerCase();
36143
+ if (!block && (tag === "h1" || tag === "h2" || tag === "h3" || tag === "blockquote" || tag === "pre")) {
36144
+ block = tag;
36145
+ }
36146
+ if (tag === "a") link = true;
36147
+ }
36148
+ node = node.parentNode;
36149
+ }
36122
36150
  return {
36123
- ...block,
36124
- id: nextBlockId(block.type),
36125
- children: block.children?.map((child) => ({
36126
- ...child,
36127
- id: nextBlockId("li")
36128
- })),
36129
- metadata: block.metadata ? { ...block.metadata } : void 0
36151
+ bold: document.queryCommandState("bold"),
36152
+ italic: document.queryCommandState("italic"),
36153
+ underline: document.queryCommandState("underline"),
36154
+ strikeThrough: document.queryCommandState("strikeThrough"),
36155
+ block,
36156
+ bullets: document.queryCommandState("insertUnorderedList"),
36157
+ numbers: document.queryCommandState("insertOrderedList"),
36158
+ link
36130
36159
  };
36131
36160
  }
36132
- function insertAfter(blocks, targetId, inserted) {
36133
- const idx = blocks.findIndex((b) => b.id === targetId);
36134
- if (idx === -1) return [...blocks, inserted];
36135
- const next = blocks.slice();
36136
- next.splice(idx + 1, 0, inserted);
36137
- return next;
36138
- }
36139
- function changeBlockType(block, type) {
36140
- if (block.type === type) return block;
36141
- if (type === "bullet-list" || type === "numbered-list") {
36142
- if (block.children && block.children.length > 0) {
36143
- return { ...block, type };
36161
+ function ToolbarButton({ icon: IconCmp, label, active, onExec }) {
36162
+ return /* @__PURE__ */ jsxRuntime.jsx(
36163
+ Button,
36164
+ {
36165
+ type: "button",
36166
+ variant: "ghost",
36167
+ size: "sm",
36168
+ "aria-label": label,
36169
+ "aria-pressed": active,
36170
+ title: label,
36171
+ className: cn(
36172
+ "h-8 w-8 p-0 gap-0 justify-center",
36173
+ active && "bg-muted text-foreground"
36174
+ ),
36175
+ onMouseDown: (e) => {
36176
+ e.preventDefault();
36177
+ onExec();
36178
+ },
36179
+ children: /* @__PURE__ */ jsxRuntime.jsx(IconCmp, { size: 15 })
36144
36180
  }
36145
- const seed2 = block.content ?? "";
36146
- return {
36147
- id: block.id,
36148
- type,
36149
- children: [
36150
- { id: nextBlockId("li"), type: "paragraph", content: seed2 }
36151
- ]
36152
- };
36153
- }
36154
- if (type === "divider") {
36155
- return { id: block.id, type };
36156
- }
36157
- if (type === "image") {
36158
- return {
36159
- id: block.id,
36160
- type,
36161
- content: "",
36162
- metadata: { url: "", caption: block.content ?? "" }
36163
- };
36164
- }
36165
- if (type === "code") {
36166
- return {
36167
- id: block.id,
36168
- type,
36169
- content: block.content ?? "",
36170
- metadata: { language: "plaintext" }
36181
+ );
36182
+ }
36183
+ var CHANGE_DEBOUNCE_MS, ALLOWED_ATTRS, DROP_TAGS, MD_BLOCK_PREFIXES, BLOCK_TAGS, MD_INLINE_RULES, RICH_TEXT_CSS, IDLE_TOOLBAR, RichTextEditor;
36184
+ var init_RichTextEditor = __esm({
36185
+ "components/core/molecules/RichTextEditor.tsx"() {
36186
+ "use client";
36187
+ init_cn();
36188
+ init_Box();
36189
+ init_Button();
36190
+ init_useEventBus();
36191
+ CHANGE_DEBOUNCE_MS = 400;
36192
+ ALLOWED_ATTRS = {
36193
+ p: [],
36194
+ h1: [],
36195
+ h2: [],
36196
+ h3: [],
36197
+ h4: [],
36198
+ ul: [],
36199
+ ol: [],
36200
+ li: [],
36201
+ blockquote: [],
36202
+ pre: [],
36203
+ code: [],
36204
+ b: [],
36205
+ strong: [],
36206
+ i: [],
36207
+ em: [],
36208
+ u: [],
36209
+ s: [],
36210
+ strike: [],
36211
+ br: [],
36212
+ hr: [],
36213
+ div: [],
36214
+ span: [],
36215
+ a: ["href"],
36216
+ img: ["src", "alt"]
36217
+ };
36218
+ DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "link", "meta", "form", "input", "button", "textarea", "select"]);
36219
+ MD_BLOCK_PREFIXES = {
36220
+ "#": { kind: "block", tag: "h1" },
36221
+ "##": { kind: "block", tag: "h2" },
36222
+ "###": { kind: "block", tag: "h3" },
36223
+ ">": { kind: "block", tag: "blockquote" },
36224
+ "```": { kind: "block", tag: "pre" },
36225
+ "-": { kind: "list", command: "insertUnorderedList" },
36226
+ "*": { kind: "list", command: "insertUnorderedList" },
36227
+ "1.": { kind: "list", command: "insertOrderedList" }
36228
+ };
36229
+ BLOCK_TAGS = /* @__PURE__ */ new Set(["P", "DIV", "LI", "H1", "H2", "H3", "H4", "BLOCKQUOTE", "PRE"]);
36230
+ MD_INLINE_RULES = [
36231
+ { pattern: /\*\*([^*\n]+)\*\*$/, wrap: "strong" },
36232
+ { pattern: /(^|[^*])\*([^*\n]+)\*$/, wrap: "em" },
36233
+ { pattern: /`([^`\n]+)`$/, wrap: "code" }
36234
+ ];
36235
+ RICH_TEXT_CSS = `
36236
+ .almadar-rich-text { line-height: 1.7; color: var(--color-foreground); }
36237
+ .almadar-rich-text h1 { font-size: 1.875rem; font-weight: 700; line-height: 1.25; margin: 1.25em 0 0.4em; }
36238
+ .almadar-rich-text h2 { font-size: 1.5rem; font-weight: 650; line-height: 1.3; margin: 1.1em 0 0.4em; }
36239
+ .almadar-rich-text h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.35; margin: 1em 0 0.35em; }
36240
+ .almadar-rich-text h1:first-child, .almadar-rich-text h2:first-child, .almadar-rich-text h3:first-child { margin-top: 0; }
36241
+ .almadar-rich-text p { margin: 0.5em 0; }
36242
+ .almadar-rich-text ul { list-style: disc; padding-inline-start: 1.5rem; margin: 0.5em 0; }
36243
+ .almadar-rich-text ol { list-style: decimal; padding-inline-start: 1.5rem; margin: 0.5em 0; }
36244
+ .almadar-rich-text li { margin: 0.25em 0; }
36245
+ .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; }
36246
+ .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; }
36247
+ .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; }
36248
+ .almadar-rich-text pre code { background: transparent; padding: 0; }
36249
+ .almadar-rich-text a { color: var(--color-primary); text-decoration: underline; }
36250
+ .almadar-rich-text hr { border: none; border-top: 1px solid var(--color-border); margin: 1.25em 0; }
36251
+ .almadar-rich-text img { max-width: 100%; border-radius: 0.375rem; }
36252
+ `;
36253
+ IDLE_TOOLBAR = {
36254
+ bold: false,
36255
+ italic: false,
36256
+ underline: false,
36257
+ strikeThrough: false,
36258
+ block: null,
36259
+ bullets: false,
36260
+ numbers: false,
36261
+ link: false
36262
+ };
36263
+ RichTextEditor = ({
36264
+ value,
36265
+ onChange,
36266
+ changeEvent,
36267
+ readOnly = false,
36268
+ placeholder,
36269
+ showToolbar = true,
36270
+ className
36271
+ }) => {
36272
+ const { t } = hooks.useTranslate();
36273
+ const ref = React85.useRef(null);
36274
+ const [toolbar, setToolbar] = React85.useState(IDLE_TOOLBAR);
36275
+ const [empty, setEmpty] = React85.useState(() => !value || !sanitizeRichHtml(value).trim());
36276
+ const eventBus = useEventBus();
36277
+ const onChangeRef = React85.useRef(onChange);
36278
+ const changeEventRef = React85.useRef(changeEvent);
36279
+ React85.useEffect(() => {
36280
+ onChangeRef.current = onChange;
36281
+ changeEventRef.current = changeEvent;
36282
+ }, [onChange, changeEvent]);
36283
+ const emitTimerRef = React85.useRef(null);
36284
+ const emitNow = React85.useCallback(() => {
36285
+ const el = ref.current;
36286
+ if (!el) return;
36287
+ const html = sanitizeRichHtml(el.innerHTML);
36288
+ onChangeRef.current?.(html);
36289
+ const evt = changeEventRef.current;
36290
+ if (evt) eventBus.emit(`UI:${evt}`, { value: html });
36291
+ }, [eventBus]);
36292
+ const scheduleEmit = React85.useCallback(() => {
36293
+ if (emitTimerRef.current) clearTimeout(emitTimerRef.current);
36294
+ emitTimerRef.current = setTimeout(() => {
36295
+ emitTimerRef.current = null;
36296
+ emitNow();
36297
+ }, CHANGE_DEBOUNCE_MS);
36298
+ }, [emitNow]);
36299
+ const flushEmit = React85.useCallback(() => {
36300
+ if (!emitTimerRef.current) return;
36301
+ clearTimeout(emitTimerRef.current);
36302
+ emitTimerRef.current = null;
36303
+ emitNow();
36304
+ }, [emitNow]);
36305
+ React85.useEffect(() => () => {
36306
+ if (emitTimerRef.current) {
36307
+ clearTimeout(emitTimerRef.current);
36308
+ emitTimerRef.current = null;
36309
+ emitNow();
36310
+ }
36311
+ }, [emitNow]);
36312
+ React85.useEffect(() => {
36313
+ if (readOnly) return;
36314
+ const el = ref.current;
36315
+ if (!el) return;
36316
+ if (document.activeElement === el) return;
36317
+ const next = sanitizeRichHtml(value ?? "") || "<p><br></p>";
36318
+ if (el.innerHTML !== next) {
36319
+ el.innerHTML = next;
36320
+ setEmpty(htmlIsEmpty(el));
36321
+ }
36322
+ }, [readOnly, value]);
36323
+ React85.useEffect(() => {
36324
+ if (readOnly) return;
36325
+ document.execCommand("defaultParagraphSeparator", false, "p");
36326
+ }, [readOnly]);
36327
+ React85.useEffect(() => {
36328
+ if (readOnly || !showToolbar) return;
36329
+ const onSelectionChange = () => {
36330
+ const el = ref.current;
36331
+ if (!el) return;
36332
+ setToolbar(readToolbarState(el));
36333
+ };
36334
+ document.addEventListener("selectionchange", onSelectionChange);
36335
+ return () => document.removeEventListener("selectionchange", onSelectionChange);
36336
+ }, [readOnly, showToolbar]);
36337
+ const tryInlineMarkdown = React85.useCallback(() => {
36338
+ const root = ref.current;
36339
+ if (!root) return false;
36340
+ const sel = window.getSelection();
36341
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
36342
+ const node = sel.anchorNode;
36343
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
36344
+ if (node.parentElement?.closest("code, pre")) return false;
36345
+ const text = (node.textContent ?? "").slice(0, sel.anchorOffset);
36346
+ for (const rule of MD_INLINE_RULES) {
36347
+ const m = rule.pattern.exec(text);
36348
+ if (!m) continue;
36349
+ const inner = rule.wrap === "em" ? m[2] : m[1];
36350
+ if (!inner || !inner.trim()) continue;
36351
+ const lead = rule.wrap === "em" ? m[1]?.length ?? 0 : 0;
36352
+ const range = document.createRange();
36353
+ range.setStart(node, m.index + lead);
36354
+ range.setEnd(node, sel.anchorOffset);
36355
+ const el = document.createElement(rule.wrap);
36356
+ el.textContent = inner;
36357
+ range.deleteContents();
36358
+ range.insertNode(el);
36359
+ const after = document.createTextNode("\u200B");
36360
+ el.parentNode?.insertBefore(after, el.nextSibling);
36361
+ const caret = document.createRange();
36362
+ caret.setStart(after, 1);
36363
+ caret.collapse(true);
36364
+ sel.removeAllRanges();
36365
+ sel.addRange(caret);
36366
+ return true;
36367
+ }
36368
+ return false;
36369
+ }, []);
36370
+ const tryBlockMarkdown = React85.useCallback(() => {
36371
+ const root = ref.current;
36372
+ if (!root) return false;
36373
+ const sel = window.getSelection();
36374
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
36375
+ const node = sel.anchorNode;
36376
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
36377
+ const block = closestBlock(node, root);
36378
+ if (block ? block.firstChild !== node : root.firstChild !== node) return false;
36379
+ const prefix = (node.textContent ?? "").slice(0, sel.anchorOffset);
36380
+ const rule = MD_BLOCK_PREFIXES[prefix];
36381
+ if (!rule) return false;
36382
+ if (node.parentElement?.closest("pre, code")) return false;
36383
+ if (rule.kind === "list" && node.parentElement?.closest("li")) return false;
36384
+ node.deleteData(0, prefix.length);
36385
+ const caret = document.createRange();
36386
+ caret.setStart(node, 0);
36387
+ caret.collapse(true);
36388
+ sel.removeAllRanges();
36389
+ sel.addRange(caret);
36390
+ if (rule.kind === "list") {
36391
+ const container = block ?? node.parentElement;
36392
+ const list = document.createElement(rule.command === "insertOrderedList" ? "ol" : "ul");
36393
+ const li = document.createElement("li");
36394
+ if (block) {
36395
+ while (block.firstChild) li.appendChild(block.firstChild);
36396
+ list.appendChild(li);
36397
+ block.replaceWith(list);
36398
+ } else if (container) {
36399
+ li.appendChild(node);
36400
+ list.appendChild(li);
36401
+ container.appendChild(list);
36402
+ }
36403
+ if (!li.firstChild) li.appendChild(document.createElement("br"));
36404
+ const liCaret = document.createRange();
36405
+ liCaret.selectNodeContents(li);
36406
+ liCaret.collapse(true);
36407
+ sel.removeAllRanges();
36408
+ sel.addRange(liCaret);
36409
+ } else {
36410
+ document.execCommand("formatBlock", false, rule.tag);
36411
+ }
36412
+ return true;
36413
+ }, []);
36414
+ const afterEdit = React85.useCallback(() => {
36415
+ const el = ref.current;
36416
+ if (!el) return;
36417
+ tryInlineMarkdown();
36418
+ setEmpty(htmlIsEmpty(el));
36419
+ setToolbar(readToolbarState(el));
36420
+ scheduleEmit();
36421
+ }, [scheduleEmit, tryInlineMarkdown]);
36422
+ const handleKeyDown = React85.useCallback((e) => {
36423
+ if (e.key === " " && tryBlockMarkdown()) {
36424
+ e.preventDefault();
36425
+ afterEdit();
36426
+ }
36427
+ }, [afterEdit, tryBlockMarkdown]);
36428
+ const execInline = React85.useCallback((command) => {
36429
+ document.execCommand(command, false);
36430
+ afterEdit();
36431
+ }, [afterEdit]);
36432
+ const execBlock = React85.useCallback((tag) => {
36433
+ const next = toolbar.block === tag ? "p" : tag;
36434
+ document.execCommand("formatBlock", false, next);
36435
+ afterEdit();
36436
+ }, [afterEdit, toolbar.block]);
36437
+ const execList = React85.useCallback((command) => {
36438
+ document.execCommand(command, false);
36439
+ afterEdit();
36440
+ }, [afterEdit]);
36441
+ const execLink = React85.useCallback(() => {
36442
+ if (toolbar.link) {
36443
+ document.execCommand("unlink", false);
36444
+ afterEdit();
36445
+ return;
36446
+ }
36447
+ const url = window.prompt(t("richTextEditor.linkPrompt"));
36448
+ if (!url) return;
36449
+ const safe = safeUrl(url, ["http://", "https://", "mailto:"]) ?? `https://${url}`;
36450
+ document.execCommand("createLink", false, safe);
36451
+ afterEdit();
36452
+ }, [afterEdit, t, toolbar.link]);
36453
+ const execRule = React85.useCallback(() => {
36454
+ document.execCommand("insertHorizontalRule", false);
36455
+ afterEdit();
36456
+ }, [afterEdit]);
36457
+ if (readOnly) {
36458
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("almadar-rich-text max-w-none", className), children: [
36459
+ /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
36460
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { dangerouslySetInnerHTML: { __html: sanitizeRichHtml(value ?? "") } })
36461
+ ] });
36462
+ }
36463
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
36464
+ /* @__PURE__ */ jsxRuntime.jsx(RichTextStyles, {}),
36465
+ showToolbar && /* @__PURE__ */ jsxRuntime.jsxs(
36466
+ Box,
36467
+ {
36468
+ role: "toolbar",
36469
+ "aria-label": t("richTextEditor.editorToolbar"),
36470
+ className: cn(
36471
+ "sticky top-0 z-10 flex flex-wrap items-center gap-0.5 self-start",
36472
+ "rounded-md border border-border bg-background/95 px-1 py-0.5 shadow-sm"
36473
+ ),
36474
+ children: [
36475
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Bold, label: t("richTextEditor.bold"), active: toolbar.bold, onExec: () => execInline("bold") }),
36476
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Italic, label: t("richTextEditor.italic"), active: toolbar.italic, onExec: () => execInline("italic") }),
36477
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Underline, label: t("richTextEditor.underline"), active: toolbar.underline, onExec: () => execInline("underline") }),
36478
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Strikethrough, label: t("richTextEditor.strikethrough"), active: toolbar.strikeThrough, onExec: () => execInline("strikeThrough") }),
36479
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
36480
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading1, label: t("richTextEditor.heading1"), active: toolbar.block === "h1", onExec: () => execBlock("h1") }),
36481
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading2, label: t("richTextEditor.heading2"), active: toolbar.block === "h2", onExec: () => execBlock("h2") }),
36482
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Heading3, label: t("richTextEditor.heading3"), active: toolbar.block === "h3", onExec: () => execBlock("h3") }),
36483
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
36484
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.List, label: t("richTextEditor.bulletList"), active: toolbar.bullets, onExec: () => execList("insertUnorderedList") }),
36485
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.ListOrdered, label: t("richTextEditor.numberedList"), active: toolbar.numbers, onExec: () => execList("insertOrderedList") }),
36486
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
36487
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
36488
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
36489
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Link, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
36490
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Minus, label: t("richTextEditor.divider"), onExec: execRule })
36491
+ ]
36492
+ }
36493
+ ),
36494
+ /* @__PURE__ */ jsxRuntime.jsx(
36495
+ Box,
36496
+ {
36497
+ ref,
36498
+ contentEditable: true,
36499
+ suppressContentEditableWarning: true,
36500
+ role: "textbox",
36501
+ "aria-multiline": "true",
36502
+ "aria-label": t("richTextEditor.editorSurface"),
36503
+ "data-placeholder": placeholder ?? t("richTextEditor.placeholder"),
36504
+ "data-empty": empty ? "true" : "false",
36505
+ className: cn(
36506
+ "almadar-rich-text max-w-none min-h-[8rem] outline-none",
36507
+ "data-[empty=true]:before:content-[attr(data-placeholder)]",
36508
+ "data-[empty=true]:before:text-muted-foreground/60",
36509
+ "data-[empty=true]:before:pointer-events-none",
36510
+ "data-[empty=true]:before:absolute",
36511
+ "relative cursor-text"
36512
+ ),
36513
+ onInput: afterEdit,
36514
+ onKeyDown: handleKeyDown,
36515
+ onBlur: flushEmit
36516
+ }
36517
+ )
36518
+ ] });
36171
36519
  };
36520
+ RichTextEditor.displayName = "RichTextEditor";
36172
36521
  }
36173
- const seed = block.children?.[0]?.content ?? block.content ?? "";
36174
- return { id: block.id, type, content: seed };
36522
+ });
36523
+ function renderIconInput4(icon, props) {
36524
+ return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
36175
36525
  }
36176
- function BlockMenu({ block, readOnly, onDelete, onDuplicate, onChangeType }) {
36526
+ function DocumentPanel({
36527
+ entity,
36528
+ title,
36529
+ subtitle,
36530
+ value,
36531
+ actions,
36532
+ maxInlineActions,
36533
+ editing = false,
36534
+ titleCommitEvent,
36535
+ contentChangeEvent,
36536
+ editEvent,
36537
+ doneEvent,
36538
+ editLabel,
36539
+ doneLabel,
36540
+ autosaveHint,
36541
+ placeholder,
36542
+ className
36543
+ }) {
36544
+ const eventBus = useEventBus();
36177
36545
  const { t } = hooks.useTranslate();
36178
- const [open, setOpen] = React85.useState(false);
36179
- const ref = React85.useRef(null);
36180
- React85.useEffect(() => {
36181
- if (!open) return;
36182
- function onDocClick(e) {
36183
- if (ref.current && !ref.current.contains(e.target)) {
36184
- setOpen(false);
36185
- }
36546
+ const [titleDraft, setTitleDraft] = React85.useState(null);
36547
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
36548
+ const actionDefs = actions ?? [];
36549
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
36550
+ const inlineActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
36551
+ const menuActions = actionDefs.filter((a) => !inlineActions.includes(a));
36552
+ const fireAction = (action) => {
36553
+ if (!action.event) return;
36554
+ eventBus.emit(`UI:${action.event}`, { id: recordId, row: entity });
36555
+ };
36556
+ const commitTitle = () => {
36557
+ if (!titleCommitEvent) return;
36558
+ const next = (titleDraft ?? "").trim();
36559
+ setTitleDraft(null);
36560
+ if (!next || next === title) return;
36561
+ eventBus.emit(`UI:${titleCommitEvent}`, { title: next, id: recordId });
36562
+ };
36563
+ const untitled = t("documentPanel.untitled") || "Untitled";
36564
+ const titleNode = titleCommitEvent && titleDraft !== null ? /* @__PURE__ */ jsxRuntime.jsx(
36565
+ Input,
36566
+ {
36567
+ value: titleDraft,
36568
+ autoFocus: true,
36569
+ "aria-label": t("common.title"),
36570
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
36571
+ onFocus: (e) => e.currentTarget.select(),
36572
+ onChange: (e) => setTitleDraft(e.target.value),
36573
+ onBlur: commitTitle,
36574
+ onKeyDown: (e) => {
36575
+ if (e.key === "Enter") {
36576
+ e.preventDefault();
36577
+ commitTitle();
36578
+ } else if (e.key === "Escape") {
36579
+ e.preventDefault();
36580
+ setTitleDraft(null);
36581
+ }
36582
+ },
36583
+ "data-testid": "document-title-input"
36186
36584
  }
36187
- document.addEventListener("mousedown", onDocClick);
36188
- return () => document.removeEventListener("mousedown", onDocClick);
36189
- }, [open]);
36190
- if (readOnly) return null;
36191
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { ref, className: "relative", children: [
36192
- /* @__PURE__ */ jsxRuntime.jsx(
36193
- Button,
36194
- {
36195
- type: "button",
36196
- variant: "ghost",
36197
- "aria-label": t("richBlockEditor.blockActions"),
36198
- className: cn(
36199
- "inline-flex items-center justify-center",
36200
- "h-6 w-6 rounded-sm p-0 gap-0",
36201
- "text-muted-foreground hover:bg-muted hover:text-foreground",
36202
- "opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
36203
- "transition-opacity"
36204
- ),
36205
- onClick: () => setOpen((v) => !v),
36206
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", className: "w-3.5 h-3.5" })
36207
- }
36208
- ),
36209
- open && /* @__PURE__ */ jsxRuntime.jsxs(
36585
+ ) : titleCommitEvent ? /* @__PURE__ */ jsxRuntime.jsx(
36586
+ Box,
36587
+ {
36588
+ role: "button",
36589
+ tabIndex: 0,
36590
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
36591
+ onClick: () => setTitleDraft(title ?? ""),
36592
+ onKeyDown: (e) => {
36593
+ if (e.key === "Enter" || e.key === " ") {
36594
+ e.preventDefault();
36595
+ setTitleDraft(title ?? "");
36596
+ }
36597
+ },
36598
+ "data-testid": "document-title-editable",
36599
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", weight: "bold", className: cn(!title && "text-muted-foreground"), children: title || untitled })
36600
+ }
36601
+ ) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", weight: "bold", children: title || untitled });
36602
+ const emitWithId = (event) => () => {
36603
+ if (event) eventBus.emit(`UI:${event}`, { id: recordId });
36604
+ };
36605
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
36606
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
36607
+ /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
36608
+ titleNode,
36609
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
36610
+ ] }),
36611
+ /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36612
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
36613
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
36614
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36615
+ editEvent && /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
36616
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
36617
+ editLabel ?? (t("documentPanel.edit") || "Edit")
36618
+ ] }),
36619
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
36620
+ Button,
36621
+ {
36622
+ variant: "primary",
36623
+ size: "sm",
36624
+ onClick: () => fireAction(action),
36625
+ "data-testid": `action-${action.event}`,
36626
+ children: [
36627
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
36628
+ action.label
36629
+ ]
36630
+ },
36631
+ idx
36632
+ )),
36633
+ menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
36634
+ Menu,
36635
+ {
36636
+ position: "bottom-end",
36637
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
36638
+ items: menuActions.map((action) => ({
36639
+ label: action.label,
36640
+ icon: action.icon,
36641
+ variant: action.variant === "danger" ? "danger" : "default",
36642
+ onClick: () => fireAction(action)
36643
+ }))
36644
+ }
36645
+ )
36646
+ ] }) })
36647
+ ] }),
36648
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
36210
36649
  Box,
36211
36650
  {
36212
- role: "menu",
36651
+ onClick: emitWithId(editEvent),
36213
36652
  className: cn(
36214
- "absolute right-0 z-10 mt-1 w-44",
36215
- "rounded-container border border-border bg-popover shadow-elevation-popover",
36216
- "py-1 text-sm"
36653
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
36654
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
36217
36655
  ),
36218
- children: [
36219
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t(BLOCK_TYPE_LABEL_KEY[block.type]) }),
36220
- /* @__PURE__ */ jsxRuntime.jsxs(
36221
- Button,
36222
- {
36223
- type: "button",
36224
- variant: "ghost",
36225
- role: "menuitem",
36226
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
36227
- onClick: () => {
36228
- onDuplicate();
36229
- setOpen(false);
36230
- },
36231
- children: [
36232
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", className: "w-3.5 h-3.5" }),
36233
- " ",
36234
- t("richBlockEditor.duplicate")
36235
- ]
36236
- }
36237
- ),
36238
- /* @__PURE__ */ jsxRuntime.jsxs(
36239
- Button,
36240
- {
36241
- type: "button",
36242
- variant: "ghost",
36243
- role: "menuitem",
36244
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-destructive hover:bg-muted justify-start rounded-none",
36245
- onClick: () => {
36246
- onDelete();
36247
- setOpen(false);
36248
- },
36249
- children: [
36250
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "trash", className: "w-3.5 h-3.5" }),
36251
- " ",
36252
- t("common.delete")
36253
- ]
36254
- }
36255
- ),
36256
- CHANGEABLE_TYPES.includes(block.type) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
36257
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "my-1 border-t border-border" }),
36258
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t("richBlockEditor.turnInto") }),
36259
- CHANGEABLE_TYPES.filter((bt) => bt !== block.type).map((bt) => /* @__PURE__ */ jsxRuntime.jsx(
36260
- Button,
36261
- {
36262
- type: "button",
36263
- variant: "ghost",
36264
- role: "menuitem",
36265
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
36266
- onClick: () => {
36267
- onChangeType(bt);
36268
- setOpen(false);
36269
- },
36270
- children: t(BLOCK_TYPE_LABEL_KEY[bt])
36271
- },
36272
- bt
36273
- ))
36274
- ] })
36275
- ]
36656
+ "data-testid": "document-body",
36657
+ children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
36276
36658
  }
36277
36659
  )
36278
- ] });
36660
+ ] }) });
36279
36661
  }
36280
- function Editable({
36281
- tag,
36282
- value,
36283
- readOnly,
36284
- placeholder,
36285
- className,
36286
- ariaLabel,
36287
- onValueChange
36288
- }) {
36289
- const ref = React85.useRef(null);
36290
- React85.useEffect(() => {
36291
- const el = ref.current;
36292
- if (!el) return;
36293
- const isFocused = document.activeElement === el;
36294
- if (!isFocused && el.textContent !== value) {
36295
- el.textContent = value;
36296
- }
36297
- }, [value]);
36298
- const handleInput = React85.useCallback(
36299
- (e) => {
36300
- onValueChange(e.currentTarget.textContent ?? "");
36301
- },
36302
- [onValueChange]
36303
- );
36304
- return /* @__PURE__ */ jsxRuntime.jsx(
36305
- Box,
36306
- {
36307
- as: tag,
36308
- ref,
36309
- contentEditable: !readOnly,
36310
- suppressContentEditableWarning: true,
36311
- role: readOnly ? void 0 : "textbox",
36312
- "aria-label": ariaLabel,
36313
- "aria-multiline": "true",
36314
- "data-placeholder": placeholder,
36315
- className: cn(
36316
- "outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-sm",
36317
- "empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground/60",
36318
- className
36319
- ),
36320
- onInput: handleInput
36321
- }
36322
- );
36662
+ var init_DocumentPanel = __esm({
36663
+ "components/core/molecules/DocumentPanel.tsx"() {
36664
+ "use client";
36665
+ init_cn();
36666
+ init_useEventBus();
36667
+ init_Box();
36668
+ init_Stack();
36669
+ init_Typography();
36670
+ init_Button();
36671
+ init_Card();
36672
+ init_Icon();
36673
+ init_Input();
36674
+ init_Menu();
36675
+ init_RichTextEditor();
36676
+ }
36677
+ });
36678
+ function renderIconInput5(icon, props) {
36679
+ return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon, ...props });
36323
36680
  }
36324
- function BlockRow({
36325
- block,
36326
- readOnly,
36327
- showAffordances,
36328
- placeholder,
36329
- onUpdate,
36330
- onDelete,
36331
- onDuplicate,
36332
- onChangeType
36681
+ function fieldName(field) {
36682
+ return field.name ?? field.key ?? "";
36683
+ }
36684
+ function relationLabel(value) {
36685
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
36686
+ for (const key of ["name", "title", "label"]) {
36687
+ const candidate = value[key];
36688
+ if (typeof candidate === "string" && candidate !== "") return candidate;
36689
+ }
36690
+ const id = value.id;
36691
+ return id !== void 0 && id !== null ? String(id) : null;
36692
+ }
36693
+ function fieldKind(field, value) {
36694
+ if (field.kind) return field.kind;
36695
+ if (field.options && field.options.length > 0) return "select";
36696
+ if (typeof value === "boolean" || field.format === "boolean") return "boolean";
36697
+ if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
36698
+ return "text";
36699
+ }
36700
+ function DocumentDetails({
36701
+ entity,
36702
+ fields,
36703
+ metaCommitEvent,
36704
+ title,
36705
+ className
36333
36706
  }) {
36707
+ const eventBus = useEventBus();
36334
36708
  const { t } = hooks.useTranslate();
36335
- const setContent = React85.useCallback(
36336
- (next) => onUpdate((b) => ({ ...b, content: next })),
36337
- [onUpdate]
36338
- );
36339
- const setMetadata = React85.useCallback(
36340
- (key, value) => onUpdate((b) => ({
36341
- ...b,
36342
- metadata: { ...b.metadata ?? {}, [key]: value }
36343
- })),
36344
- [onUpdate]
36345
- );
36346
- const setChildContent = React85.useCallback(
36347
- (childId, next) => onUpdate((b) => ({
36348
- ...b,
36349
- children: (b.children ?? []).map(
36350
- (c) => c.id === childId ? { ...c, content: next } : c
36351
- )
36352
- })),
36353
- [onUpdate]
36354
- );
36355
- const addListItem = React85.useCallback(
36356
- () => onUpdate((b) => ({
36357
- ...b,
36358
- children: [
36359
- ...b.children ?? [],
36360
- { id: nextBlockId("li"), type: "paragraph", content: "" }
36361
- ]
36362
- })),
36363
- [onUpdate]
36364
- );
36365
- const removeListItem = React85.useCallback(
36366
- (childId) => onUpdate((b) => {
36367
- const remaining = (b.children ?? []).filter((c) => c.id !== childId);
36368
- return {
36369
- ...b,
36370
- children: remaining.length === 0 ? [{ id: nextBlockId("li"), type: "paragraph", content: "" }] : remaining
36371
- };
36372
- }),
36373
- [onUpdate]
36374
- );
36375
- const renderBody = () => {
36376
- switch (block.type) {
36377
- case "heading-1":
36378
- return /* @__PURE__ */ jsxRuntime.jsx(
36379
- Editable,
36380
- {
36381
- tag: "h1",
36382
- value: block.content ?? "",
36383
- readOnly,
36384
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading1"),
36385
- ariaLabel: t("richBlockEditor.aria.heading1Block"),
36386
- className: "text-3xl font-bold leading-tight",
36387
- onValueChange: setContent
36388
- }
36389
- );
36390
- case "heading-2":
36391
- return /* @__PURE__ */ jsxRuntime.jsx(
36392
- Editable,
36393
- {
36394
- tag: "h2",
36395
- value: block.content ?? "",
36396
- readOnly,
36397
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading2"),
36398
- ariaLabel: t("richBlockEditor.aria.heading2Block"),
36399
- className: "text-2xl font-semibold leading-tight",
36400
- onValueChange: setContent
36401
- }
36402
- );
36403
- case "heading-3":
36404
- return /* @__PURE__ */ jsxRuntime.jsx(
36405
- Editable,
36406
- {
36407
- tag: "h3",
36408
- value: block.content ?? "",
36409
- readOnly,
36410
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading3"),
36411
- ariaLabel: t("richBlockEditor.aria.heading3Block"),
36412
- className: "text-xl font-semibold leading-tight",
36413
- onValueChange: setContent
36414
- }
36415
- );
36416
- case "quote":
36417
- return /* @__PURE__ */ jsxRuntime.jsx(
36418
- Editable,
36419
- {
36420
- tag: "blockquote",
36421
- value: block.content ?? "",
36422
- readOnly,
36423
- placeholder: placeholder ?? t("richBlockEditor.placeholder.quote"),
36424
- ariaLabel: t("richBlockEditor.aria.quoteBlock"),
36425
- className: "border-l-4 border-primary/60 pl-4 italic text-muted-foreground",
36426
- onValueChange: setContent
36427
- }
36428
- );
36429
- case "code":
36430
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "rounded-md border border-border bg-muted/40", children: [
36431
- /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex items-center justify-between border-b border-border px-3 py-1 text-xs text-muted-foreground", children: [
36432
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { as: "span", variant: "caption", className: "uppercase tracking-wide", children: t("richBlockEditor.blockType.code") }),
36433
- !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
36434
- Input,
36435
- {
36436
- inputType: "text",
36437
- value: String(block.metadata?.language ?? "plaintext"),
36438
- "aria-label": t("richBlockEditor.aria.codeLanguage"),
36439
- className: cn(
36440
- "h-6 w-32 rounded-sm border border-border bg-background",
36441
- "px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
36442
- ),
36443
- onChange: (e) => setMetadata("language", e.target.value)
36444
- }
36445
- ),
36446
- readOnly && /* @__PURE__ */ jsxRuntime.jsx(Typography, { as: "span", variant: "caption", className: "text-xs", children: String(block.metadata?.language ?? "plaintext") })
36447
- ] }),
36448
- /* @__PURE__ */ jsxRuntime.jsx(
36449
- Editable,
36450
- {
36451
- tag: "pre",
36452
- value: block.content ?? "",
36453
- readOnly,
36454
- placeholder: placeholder ?? t("richBlockEditor.placeholder.code"),
36455
- ariaLabel: t("richBlockEditor.aria.codeBlock"),
36456
- className: "block whitespace-pre-wrap p-3 font-mono text-sm leading-relaxed",
36457
- onValueChange: setContent
36458
- }
36459
- )
36460
- ] });
36461
- case "divider":
36462
- return /* @__PURE__ */ jsxRuntime.jsx(Divider, { className: "my-2" });
36463
- case "image": {
36464
- const url = String(block.metadata?.url ?? "");
36465
- const caption = String(block.metadata?.caption ?? "");
36466
- const imgProps = {
36467
- src: url,
36468
- alt: caption || t("richBlockEditor.embeddedImage"),
36469
- className: "max-h-96 w-full rounded-md border border-border object-contain"
36470
- };
36471
- return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "space-y-2", children: [
36472
- url ? /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "img", ...imgProps }) : /* @__PURE__ */ jsxRuntime.jsxs(
36473
- Box,
36474
- {
36475
- className: cn(
36476
- "flex h-32 items-center justify-center",
36477
- "rounded-md border border-dashed border-border",
36478
- "text-sm text-muted-foreground"
36479
- ),
36480
- children: [
36481
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "image", className: "mr-2 w-4 h-4" }),
36482
- " ",
36483
- t("richBlockEditor.noImageUrl")
36484
- ]
36485
- }
36486
- ),
36487
- !readOnly && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex flex-col gap-2 sm:flex-row", children: [
36488
- /* @__PURE__ */ jsxRuntime.jsx(
36489
- Input,
36490
- {
36491
- inputType: "url",
36492
- value: url,
36493
- placeholder: "https://example.com/image.png",
36494
- "aria-label": t("richBlockEditor.aria.imageUrl"),
36495
- className: cn(
36496
- "h-8 flex-1 rounded-sm border border-border bg-background",
36497
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
36498
- ),
36499
- onChange: (e) => setMetadata("url", e.target.value)
36500
- }
36501
- ),
36502
- /* @__PURE__ */ jsxRuntime.jsx(
36503
- Input,
36504
- {
36505
- inputType: "text",
36506
- value: caption,
36507
- placeholder: t("richBlockEditor.placeholder.caption"),
36508
- "aria-label": t("richBlockEditor.aria.imageCaption"),
36509
- className: cn(
36510
- "h-8 flex-1 rounded-sm border border-border bg-background",
36511
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
36512
- ),
36513
- onChange: (e) => setMetadata("caption", e.target.value)
36514
- }
36515
- )
36516
- ] }),
36517
- readOnly && caption && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-center text-muted-foreground", children: caption })
36518
- ] });
36709
+ const [fieldDraft, setFieldDraft] = React85.useState(null);
36710
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
36711
+ const fieldDefs = (fields ?? []).filter((f3) => fieldName(f3) !== "");
36712
+ if (fieldDefs.length === 0) return null;
36713
+ const commitField = (name, next, current) => {
36714
+ setFieldDraft(null);
36715
+ if (!metaCommitEvent || next === current) return;
36716
+ eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
36717
+ };
36718
+ const renderValue = (field) => {
36719
+ const name = fieldName(field);
36720
+ const raw = getNestedValue(entity ?? {}, name);
36721
+ const kind = fieldKind(field, raw);
36722
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
36723
+ if (kind === "boolean") {
36724
+ return /* @__PURE__ */ jsxRuntime.jsx(
36725
+ Switch,
36726
+ {
36727
+ checked: Boolean(raw),
36728
+ disabled: !metaCommitEvent,
36729
+ "aria-label": label,
36730
+ onChange: (checked) => commitField(name, checked, Boolean(raw))
36731
+ }
36732
+ );
36733
+ }
36734
+ if (kind === "select") {
36735
+ return /* @__PURE__ */ jsxRuntime.jsx(
36736
+ Select,
36737
+ {
36738
+ value: raw !== void 0 && raw !== null ? String(raw) : "",
36739
+ disabled: !metaCommitEvent,
36740
+ "aria-label": label,
36741
+ className: "h-8 w-full",
36742
+ options: (field.options ?? []).map((opt) => ({ value: opt, label: humanizeEnumValue(opt) })),
36743
+ onChange: (e) => commitField(name, e.target.value, raw)
36744
+ }
36745
+ );
36746
+ }
36747
+ if (kind === "readonly" || !metaCommitEvent) {
36748
+ if (Array.isArray(raw)) {
36749
+ if (raw.length === 0) {
36750
+ return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
36751
+ }
36752
+ return /* @__PURE__ */ jsxRuntime.jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
36519
36753
  }
36520
- case "bullet-list":
36521
- case "numbered-list": {
36522
- const items = block.children ?? [];
36523
- return /* @__PURE__ */ jsxRuntime.jsxs(
36524
- Box,
36525
- {
36526
- as: block.type === "bullet-list" ? "ul" : "ol",
36527
- className: cn(
36528
- "space-y-1 pl-6",
36529
- block.type === "bullet-list" ? "list-disc" : "list-decimal"
36530
- ),
36531
- children: [
36532
- items.map((child) => /* @__PURE__ */ jsxRuntime.jsxs(Box, { as: "li", className: "group/item flex items-start gap-2", children: [
36533
- /* @__PURE__ */ jsxRuntime.jsx(
36534
- Editable,
36535
- {
36536
- tag: "span",
36537
- value: child.content ?? "",
36538
- readOnly,
36539
- placeholder: t("richBlockEditor.placeholder.listItem"),
36540
- ariaLabel: t("richBlockEditor.aria.listItem"),
36541
- className: "inline-block min-w-[1ch] flex-1",
36542
- onValueChange: (next) => setChildContent(child.id, next)
36543
- }
36544
- ),
36545
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(
36546
- Button,
36547
- {
36548
- type: "button",
36549
- variant: "ghost",
36550
- "aria-label": t("richBlockEditor.aria.removeListItem"),
36551
- className: cn(
36552
- "h-5 w-5 shrink-0 rounded-sm text-muted-foreground p-0 gap-0",
36553
- "opacity-0 group-hover/item:opacity-100 hover:bg-muted hover:text-foreground"
36554
- ),
36555
- onClick: () => removeListItem(child.id),
36556
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "trash", className: "w-3 h-3" })
36557
- }
36558
- )
36559
- ] }, child.id)),
36560
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "li", className: "list-none pl-0", children: /* @__PURE__ */ jsxRuntime.jsxs(
36561
- Button,
36562
- {
36563
- type: "button",
36564
- variant: "ghost",
36565
- className: cn(
36566
- "inline-flex items-center gap-1 text-xs text-muted-foreground",
36567
- "hover:text-foreground p-0 h-auto"
36568
- ),
36569
- onClick: addListItem,
36570
- children: [
36571
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", className: "w-3 h-3" }),
36572
- " ",
36573
- t("richBlockEditor.addItem")
36574
- ]
36575
- }
36576
- ) })
36577
- ]
36754
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
36755
+ return /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
36756
+ }
36757
+ if (fieldDraft?.name === name) {
36758
+ return /* @__PURE__ */ jsxRuntime.jsx(
36759
+ Input,
36760
+ {
36761
+ value: fieldDraft.value,
36762
+ autoFocus: true,
36763
+ "aria-label": label,
36764
+ className: "h-8 w-full",
36765
+ onChange: (e) => setFieldDraft({ name, value: e.target.value }),
36766
+ onBlur: () => commitField(name, fieldDraft.value.trim(), raw),
36767
+ onKeyDown: (e) => {
36768
+ if (e.key === "Enter") {
36769
+ e.preventDefault();
36770
+ commitField(name, fieldDraft.value.trim(), raw);
36771
+ } else if (e.key === "Escape") {
36772
+ e.preventDefault();
36773
+ setFieldDraft(null);
36774
+ }
36775
+ },
36776
+ "data-testid": `document-property-input-${name}`
36777
+ }
36778
+ );
36779
+ }
36780
+ const shown = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
36781
+ return /* @__PURE__ */ jsxRuntime.jsx(
36782
+ Box,
36783
+ {
36784
+ role: "button",
36785
+ tabIndex: 0,
36786
+ className: cn(
36787
+ "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
36788
+ (raw === void 0 || raw === null || raw === "") && "text-muted-foreground"
36789
+ ),
36790
+ onClick: () => setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" }),
36791
+ onKeyDown: (e) => {
36792
+ if (e.key === "Enter" || e.key === " ") {
36793
+ e.preventDefault();
36794
+ setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" });
36578
36795
  }
36579
- );
36796
+ },
36797
+ "data-testid": `document-property-${name}`,
36798
+ children: /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "break-words", children: shown })
36580
36799
  }
36581
- case "paragraph":
36582
- default:
36583
- return /* @__PURE__ */ jsxRuntime.jsx(
36584
- Editable,
36585
- {
36586
- tag: "p",
36587
- value: block.content ?? "",
36588
- readOnly,
36589
- placeholder: placeholder ?? t("richBlockEditor.placeholder.paragraph"),
36590
- ariaLabel: t("richBlockEditor.aria.paragraphBlock"),
36591
- className: "leading-7",
36592
- onValueChange: setContent
36593
- }
36594
- );
36595
- }
36800
+ );
36596
36801
  };
36597
- return /* @__PURE__ */ jsxRuntime.jsxs(
36598
- Box,
36599
- {
36600
- className: cn(
36601
- "group relative flex items-start gap-2 rounded-sm",
36602
- "px-2 py-1 hover:bg-muted/30"
36603
- ),
36604
- "data-block-id": block.id,
36605
- "data-block-type": block.type,
36606
- children: [
36607
- !readOnly && showAffordances && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex w-8 shrink-0 items-center pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(
36608
- BlockMenu,
36609
- {
36610
- block,
36611
- readOnly,
36612
- onDelete,
36613
- onDuplicate,
36614
- onChangeType
36615
- }
36616
- ) }),
36617
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "min-w-0 flex-1", children: renderBody() })
36618
- ]
36619
- }
36620
- );
36802
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { variant: "bordered", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", className: "p-4", children: [
36803
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "secondary", weight: "medium", className: "uppercase tracking-wide", children: title ?? (t("documentDetails.title") || "Details") }),
36804
+ /* @__PURE__ */ jsxRuntime.jsx(VStack, { gap: "sm", children: fieldDefs.map((field) => {
36805
+ const name = fieldName(field);
36806
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
36807
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", children: [
36808
+ /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "xs", className: "items-center", children: [
36809
+ field.icon && renderIconInput5(field.icon, { size: "xs", className: "text-muted-foreground" }),
36810
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", children: label })
36811
+ ] }),
36812
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "min-w-0", children: renderValue(field) })
36813
+ ] }, name);
36814
+ }) })
36815
+ ] }) });
36621
36816
  }
36622
- var TOOLBAR_ENTRIES, BLOCK_TYPE_LABEL_KEY, CHANGEABLE_TYPES, _idSeq, BLOCK_TYPES, RichBlockEditor;
36623
- var init_RichBlockEditor = __esm({
36624
- "components/core/molecules/RichBlockEditor.tsx"() {
36817
+ var init_DocumentDetails = __esm({
36818
+ "components/core/molecules/DocumentDetails.tsx"() {
36625
36819
  "use client";
36626
36820
  init_cn();
36627
- init_Card();
36628
- init_Typography();
36629
- init_Button();
36821
+ init_format();
36822
+ init_getNestedValue();
36823
+ init_useEventBus();
36630
36824
  init_Box();
36631
- init_Divider();
36632
- init_Input();
36825
+ init_Stack();
36826
+ init_Typography();
36827
+ init_Badge();
36828
+ init_Card();
36633
36829
  init_Icon();
36634
- init_useEventBus();
36635
- TOOLBAR_ENTRIES = [
36636
- { type: "paragraph", labelKey: "richBlockEditor.toolbar.text", icon: LucideIcons2.Type },
36637
- { type: "heading-1", labelKey: "richBlockEditor.toolbar.h1", icon: LucideIcons2.Heading1 },
36638
- { type: "heading-2", labelKey: "richBlockEditor.toolbar.h2", icon: LucideIcons2.Heading2 },
36639
- { type: "heading-3", labelKey: "richBlockEditor.toolbar.h3", icon: LucideIcons2.Heading3 },
36640
- { type: "bullet-list", labelKey: "richBlockEditor.toolbar.bulletList", icon: LucideIcons2.List },
36641
- { type: "numbered-list", labelKey: "richBlockEditor.toolbar.numbered", icon: LucideIcons2.ListOrdered },
36642
- { type: "quote", labelKey: "richBlockEditor.toolbar.quote", icon: LucideIcons2.Quote },
36643
- { type: "code", labelKey: "richBlockEditor.toolbar.code", icon: LucideIcons2.Code },
36644
- { type: "divider", labelKey: "richBlockEditor.toolbar.divider", icon: LucideIcons2.Minus },
36645
- { type: "image", labelKey: "richBlockEditor.toolbar.image", icon: LucideIcons2.Image }
36646
- ];
36647
- BLOCK_TYPE_LABEL_KEY = {
36648
- paragraph: "richBlockEditor.blockType.paragraph",
36649
- "heading-1": "richBlockEditor.blockType.heading1",
36650
- "heading-2": "richBlockEditor.blockType.heading2",
36651
- "heading-3": "richBlockEditor.blockType.heading3",
36652
- "bullet-list": "richBlockEditor.blockType.bulletList",
36653
- "numbered-list": "richBlockEditor.blockType.numberedList",
36654
- quote: "richBlockEditor.blockType.quote",
36655
- code: "richBlockEditor.blockType.code",
36656
- divider: "richBlockEditor.blockType.divider",
36657
- image: "richBlockEditor.blockType.image"
36658
- };
36659
- CHANGEABLE_TYPES = [
36660
- "paragraph",
36661
- "heading-1",
36662
- "heading-2",
36663
- "heading-3",
36664
- "bullet-list",
36665
- "numbered-list",
36666
- "quote",
36667
- "code"
36668
- ];
36669
- _idSeq = 0;
36670
- BLOCK_TYPES = /* @__PURE__ */ new Set([
36671
- "paragraph",
36672
- "heading-1",
36673
- "heading-2",
36674
- "heading-3",
36675
- "bullet-list",
36676
- "numbered-list",
36677
- "quote",
36678
- "code",
36679
- "divider",
36680
- "image"
36681
- ]);
36682
- RichBlockEditor = ({
36683
- initialBlocks,
36684
- onChange,
36685
- changeEvent,
36686
- readOnly = false,
36687
- placeholder,
36688
- enableBlocks = true,
36689
- showToolbar = true,
36690
- className
36691
- }) => {
36692
- const { t } = hooks.useTranslate();
36693
- const [blocks, setBlocks] = React85.useState(
36694
- () => normalizeBlocks(initialBlocks)
36695
- );
36696
- const onChangeRef = React85.useRef(onChange);
36697
- React85.useEffect(() => {
36698
- onChangeRef.current = onChange;
36699
- }, [onChange]);
36700
- const eventBus = useEventBus();
36701
- const changeEventRef = React85.useRef(changeEvent);
36702
- React85.useEffect(() => {
36703
- changeEventRef.current = changeEvent;
36704
- }, [changeEvent]);
36705
- const commit = React85.useCallback((next) => {
36706
- setBlocks(next);
36707
- onChangeRef.current?.(next);
36708
- const evt = changeEventRef.current;
36709
- if (evt) eventBus.emit(`UI:${evt}`, { blocks: next });
36710
- }, [eventBus]);
36711
- const handleAppend = React85.useCallback(
36712
- (type) => {
36713
- if (readOnly) return;
36714
- commit([...blocks, createBlock(type)]);
36715
- },
36716
- [blocks, commit, readOnly]
36717
- );
36718
- const handleUpdate = React85.useCallback(
36719
- (id, updater) => {
36720
- commit(replaceBlock(blocks, id, updater));
36721
- },
36722
- [blocks, commit]
36723
- );
36724
- const handleDelete = React85.useCallback(
36725
- (id) => {
36726
- const next = removeBlock(blocks, id);
36727
- commit(next.length > 0 ? next : [createBlock("paragraph")]);
36728
- },
36729
- [blocks, commit]
36730
- );
36731
- const handleDuplicate = React85.useCallback(
36732
- (id) => {
36733
- const target = blocks.find((b) => b.id === id);
36734
- if (!target) return;
36735
- commit(insertAfter(blocks, id, duplicateBlock(target)));
36736
- },
36737
- [blocks, commit]
36738
- );
36739
- const handleChangeType = React85.useCallback(
36740
- (id, type) => {
36741
- commit(
36742
- replaceBlock(blocks, id, (b) => changeBlockType(b, type))
36743
- );
36744
- },
36745
- [blocks, commit]
36746
- );
36747
- return /* @__PURE__ */ jsxRuntime.jsxs(
36748
- Card,
36749
- {
36750
- variant: "bordered",
36751
- padding: "none",
36752
- className: cn("flex flex-col text-card-foreground", className),
36753
- children: [
36754
- enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
36755
- Box,
36756
- {
36757
- role: "toolbar",
36758
- "aria-label": t("richBlockEditor.editorToolbar"),
36759
- className: cn(
36760
- "flex flex-wrap items-center gap-1",
36761
- "border-b border-border bg-muted/30 px-2 py-2"
36762
- ),
36763
- children: TOOLBAR_ENTRIES.map((entry) => {
36764
- const Icon2 = entry.icon;
36765
- const entryLabel = t(entry.labelKey);
36766
- return /* @__PURE__ */ jsxRuntime.jsxs(
36767
- Button,
36768
- {
36769
- type: "button",
36770
- variant: "ghost",
36771
- size: "sm",
36772
- "aria-label": t("richBlockEditor.insertEntry", { label: entryLabel }),
36773
- title: entryLabel,
36774
- onClick: () => handleAppend(entry.type),
36775
- children: [
36776
- /* @__PURE__ */ jsxRuntime.jsx(Icon2, { size: 14 }),
36777
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { as: "span", variant: "caption", className: "ml-1 hidden text-xs sm:inline", children: entryLabel })
36778
- ]
36779
- },
36780
- entry.type
36781
- );
36782
- })
36783
- }
36784
- ),
36785
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "flex flex-col gap-1 px-3 py-3", children: blocks.map((block) => /* @__PURE__ */ jsxRuntime.jsx(
36786
- BlockRow,
36787
- {
36788
- block,
36789
- readOnly,
36790
- showAffordances: enableBlocks,
36791
- placeholder,
36792
- onUpdate: (updater) => handleUpdate(block.id, updater),
36793
- onDelete: () => handleDelete(block.id),
36794
- onDuplicate: () => handleDuplicate(block.id),
36795
- onChangeType: (type) => handleChangeType(block.id, type)
36796
- },
36797
- block.id
36798
- )) })
36799
- ]
36800
- }
36801
- );
36802
- };
36803
- RichBlockEditor.displayName = "RichBlockEditor";
36830
+ init_Input();
36831
+ init_Select();
36832
+ init_Switch();
36804
36833
  }
36805
36834
  });
36806
36835
  function collectInitiallyCollapsed(nodes, acc) {
@@ -41749,8 +41778,8 @@ var init_DataTable = __esm({
41749
41778
  DataTable.displayName = "DataTable";
41750
41779
  }
41751
41780
  });
41752
- function getBadgeVariant(fieldName, value) {
41753
- const name = fieldName.toLowerCase();
41781
+ function getBadgeVariant(fieldName2, value) {
41782
+ const name = fieldName2.toLowerCase();
41754
41783
  const val = String(value).toLowerCase();
41755
41784
  if (name.includes("status")) {
41756
41785
  if (val.includes("complete") || val.includes("done") || val.includes("active"))
@@ -41766,12 +41795,12 @@ function getBadgeVariant(fieldName, value) {
41766
41795
  }
41767
41796
  return "default";
41768
41797
  }
41769
- function formatFieldValue2(value, fieldName) {
41798
+ function formatFieldValue2(value, fieldName2) {
41770
41799
  if (typeof value === "number") {
41771
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
41800
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
41772
41801
  return `${value}%`;
41773
41802
  }
41774
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
41803
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
41775
41804
  return `$${value.toLocaleString()}`;
41776
41805
  }
41777
41806
  return value.toLocaleString();
@@ -41781,7 +41810,7 @@ function formatFieldValue2(value, fieldName) {
41781
41810
  }
41782
41811
  return String(value);
41783
41812
  }
41784
- function renderRichFieldValue(value, fieldName, fieldType, meta) {
41813
+ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
41785
41814
  if (value === void 0 || value === null) return "\u2014";
41786
41815
  const str = String(value);
41787
41816
  switch (fieldType) {
@@ -41792,7 +41821,7 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
41792
41821
  "img",
41793
41822
  {
41794
41823
  src: str,
41795
- alt: formatFieldLabel(fieldName),
41824
+ alt: formatFieldLabel(fieldName2),
41796
41825
  className: "max-w-full max-h-64 rounded-md object-contain",
41797
41826
  loading: "lazy"
41798
41827
  }
@@ -41927,9 +41956,9 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
41927
41956
  return /* @__PURE__ */ jsxRuntime.jsx("a", { href: `tel:${str}`, className: "text-primary hover:underline", children: str });
41928
41957
  default:
41929
41958
  if (meta?.values && meta.values.length > 0 && meta.values.includes(str)) {
41930
- return /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: getBadgeVariant(fieldName, str), children: humanizeEnumValue(str) });
41959
+ return /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: getBadgeVariant(fieldName2, str), children: humanizeEnumValue(str) });
41931
41960
  }
41932
- return formatFieldValue2(value, fieldName);
41961
+ return formatFieldValue2(value, fieldName2);
41933
41962
  }
41934
41963
  }
41935
41964
  function normalizeFieldDefs(fields) {
@@ -42399,16 +42428,11 @@ var init_DetailPanel = __esm({
42399
42428
  footer
42400
42429
  ] })
42401
42430
  ] }) });
42402
- return /* @__PURE__ */ jsxRuntime.jsx(
42403
- Box,
42404
- {
42405
- className: cn(
42406
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
42407
- className
42408
- ),
42409
- children: content
42410
- }
42411
- );
42431
+ if (!slideOver) {
42432
+ return /* @__PURE__ */ jsxRuntime.jsx(Box, { className, children: content });
42433
+ }
42434
+ const panel = /* @__PURE__ */ jsxRuntime.jsx(Box, { className: cn("fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6", className), children: content });
42435
+ return typeof document === "undefined" ? panel : reactDom.createPortal(panel, document.body);
42412
42436
  };
42413
42437
  DetailPanel.displayName = "DetailPanel";
42414
42438
  }
@@ -42777,7 +42801,21 @@ var init_Form = __esm({
42777
42801
  const normalizedInitialData = React85__namespace.default.useMemo(() => {
42778
42802
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
42779
42803
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
42780
- return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
42804
+ const merged = entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
42805
+ const toId = (value) => {
42806
+ if (value === null || value === void 0 || typeof value !== "object" || value instanceof Date) return value;
42807
+ if (Array.isArray(value)) {
42808
+ return value.map(
42809
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && item.id !== void 0 && item.id !== null ? String(item.id) : item
42810
+ );
42811
+ }
42812
+ return value.id !== void 0 && value.id !== null ? String(value.id) : value;
42813
+ };
42814
+ const normalized = {};
42815
+ for (const [key, value] of Object.entries(merged)) {
42816
+ normalized[key] = key === "id" ? value : toId(value);
42817
+ }
42818
+ return normalized;
42781
42819
  }, [entity, initialData]);
42782
42820
  const entityDerivedFields = React85__namespace.default.useMemo(() => {
42783
42821
  if (fields && fields.length > 0) return void 0;
@@ -42906,8 +42944,8 @@ var init_Form = __esm({
42906
42944
  checkViolations(name, newFormData);
42907
42945
  };
42908
42946
  const isFieldVisible = React85__namespace.default.useCallback(
42909
- (fieldName) => {
42910
- const condition = conditionalFields[fieldName];
42947
+ (fieldName2) => {
42948
+ const condition = conditionalFields[fieldName2];
42911
42949
  if (!condition) return true;
42912
42950
  return Boolean(evaluateFormExpression(condition, evalContext));
42913
42951
  },
@@ -42953,9 +42991,9 @@ var init_Form = __esm({
42953
42991
  };
42954
42992
  const handleInvalid = (e) => {
42955
42993
  const target = e.target;
42956
- const fieldName = target.getAttribute("data-field-name") ?? target.name ?? "";
42994
+ const fieldName2 = target.getAttribute("data-field-name") ?? target.name ?? "";
42957
42995
  const fieldMessage = target.validationMessage || "Invalid value";
42958
- debug("forms", "invalid", { mode: formMode, fieldName, fieldMessage });
42996
+ debug("forms", "invalid", { mode: formMode, fieldName: fieldName2, fieldMessage });
42959
42997
  queueMicrotask(() => {
42960
42998
  const form = formRef.current;
42961
42999
  if (!form) return;
@@ -42991,22 +43029,22 @@ var init_Form = __esm({
42991
43029
  };
42992
43030
  const renderField = React85__namespace.default.useCallback(
42993
43031
  (field) => {
42994
- const fieldName = field.name || field.field;
42995
- if (!fieldName) return null;
42996
- if (!isFieldVisible(fieldName)) {
43032
+ const fieldName2 = field.name || field.field;
43033
+ if (!fieldName2) return null;
43034
+ if (!isFieldVisible(fieldName2)) {
42997
43035
  return null;
42998
43036
  }
42999
43037
  const inputType = determineInputType(field);
43000
- const label = field.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, " $1");
43001
- const currentValue = formData[fieldName] ?? field.defaultValue ?? "";
43002
- return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", "data-field": fieldName, children: [
43038
+ const label = field.label || fieldName2.charAt(0).toUpperCase() + fieldName2.slice(1).replace(/([A-Z])/g, " $1");
43039
+ const currentValue = formData[fieldName2] ?? field.defaultValue ?? "";
43040
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "xs", "data-field": fieldName2, children: [
43003
43041
  inputType !== "checkbox" && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { as: "label", variant: "label", weight: "bold", children: [
43004
43042
  label,
43005
43043
  field.required && /* @__PURE__ */ jsxRuntime.jsx(Typography, { as: "span", color: "error", className: "ml-1", children: "*" })
43006
43044
  ] }),
43007
- renderFieldInput(field, fieldName, inputType, currentValue, label),
43045
+ renderFieldInput(field, fieldName2, inputType, currentValue, label),
43008
43046
  field.hint && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", color: "muted", children: field.hint })
43009
- ] }, fieldName);
43047
+ ] }, fieldName2);
43010
43048
  },
43011
43049
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
43012
43050
  );
@@ -43035,8 +43073,8 @@ var init_Form = __esm({
43035
43073
  }
43036
43074
  return field;
43037
43075
  }).map((field) => {
43038
- const fieldName = field.name || field.field;
43039
- const override = fieldOverrides?.find((o) => o.name === fieldName);
43076
+ const fieldName2 = field.name || field.field;
43077
+ const override = fieldOverrides?.find((o) => o.name === fieldName2);
43040
43078
  if (!override) return field;
43041
43079
  return {
43042
43080
  ...field,
@@ -43092,11 +43130,11 @@ var init_Form = __esm({
43092
43130
  ] }, section.id);
43093
43131
  }).filter(Boolean);
43094
43132
  }, [sections, isSectionVisible, collapsedSections, renderField, gap]);
43095
- function renderFieldInput(field, fieldName, inputType, currentValue, label) {
43133
+ function renderFieldInput(field, fieldName2, inputType, currentValue, label) {
43096
43134
  const commonProps = {
43097
- id: fieldName,
43098
- name: fieldName,
43099
- "data-field-name": fieldName,
43135
+ id: fieldName2,
43136
+ name: fieldName2,
43137
+ "data-field-name": fieldName2,
43100
43138
  required: field.required,
43101
43139
  disabled: isLoading,
43102
43140
  placeholder: field.placeholder,
@@ -43110,7 +43148,7 @@ var init_Form = __esm({
43110
43148
  ...commonProps,
43111
43149
  label: label + (field.required ? " *" : ""),
43112
43150
  checked: Boolean(currentValue),
43113
- onChange: (e) => handleChange(fieldName, e.target.checked)
43151
+ onChange: (e) => handleChange(fieldName2, e.target.checked)
43114
43152
  }
43115
43153
  );
43116
43154
  case "textarea":
@@ -43119,7 +43157,7 @@ var init_Form = __esm({
43119
43157
  {
43120
43158
  ...commonProps,
43121
43159
  value: String(currentValue),
43122
- onChange: (e) => handleChange(fieldName, e.target.value),
43160
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43123
43161
  minLength: field.min,
43124
43162
  maxLength: field.max
43125
43163
  }
@@ -43132,14 +43170,14 @@ var init_Form = __esm({
43132
43170
  ...commonProps,
43133
43171
  options,
43134
43172
  value: String(currentValue),
43135
- onValueChange: (v) => handleChange(fieldName, v),
43173
+ onValueChange: (v) => handleChange(fieldName2, v),
43136
43174
  placeholder: field.placeholder || `Select ${label}...`
43137
43175
  }
43138
43176
  );
43139
43177
  }
43140
43178
  case "relation": {
43141
- const relationOptions = relationsData[fieldName] || [];
43142
- const relationLoading = relationsLoading[fieldName] || false;
43179
+ const relationOptions = relationsData[fieldName2] || [];
43180
+ const relationLoading = relationsLoading[fieldName2] || false;
43143
43181
  if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
43144
43182
  const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
43145
43183
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -43151,7 +43189,7 @@ var init_Form = __esm({
43151
43189
  clearable: true,
43152
43190
  options: [...relationOptions],
43153
43191
  value: selectedValues,
43154
- onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
43192
+ onValueChange: (value) => handleChange(fieldName2, Array.isArray(value) ? value : [value]),
43155
43193
  placeholder: field.placeholder || `Select ${label}...`
43156
43194
  }
43157
43195
  );
@@ -43161,7 +43199,7 @@ var init_Form = __esm({
43161
43199
  {
43162
43200
  ...commonProps,
43163
43201
  value: currentValue ? String(currentValue) : void 0,
43164
- onChange: (value) => handleChange(fieldName, value),
43202
+ onChange: (value) => handleChange(fieldName2, value),
43165
43203
  options: relationOptions,
43166
43204
  isLoading: relationLoading,
43167
43205
  placeholder: field.placeholder || `Select ${label}...`,
@@ -43178,7 +43216,7 @@ var init_Form = __esm({
43178
43216
  placeholder: field.placeholder,
43179
43217
  disabled: isLoading,
43180
43218
  value: arrayValue,
43181
- onChange: (next) => handleChange(fieldName, [...next])
43219
+ onChange: (next) => handleChange(fieldName2, [...next])
43182
43220
  }
43183
43221
  );
43184
43222
  }
@@ -43190,7 +43228,7 @@ var init_Form = __esm({
43190
43228
  type: "number",
43191
43229
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
43192
43230
  onChange: (e) => handleChange(
43193
- fieldName,
43231
+ fieldName2,
43194
43232
  e.target.value ? Number(e.target.value) : void 0
43195
43233
  ),
43196
43234
  min: field.min,
@@ -43207,7 +43245,7 @@ var init_Form = __esm({
43207
43245
  icon: LucideIcons2.DollarSign,
43208
43246
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
43209
43247
  onChange: (e) => handleChange(
43210
- fieldName,
43248
+ fieldName2,
43211
43249
  e.target.value ? Number(e.target.value) : void 0
43212
43250
  ),
43213
43251
  min: field.min,
@@ -43225,7 +43263,7 @@ var init_Form = __esm({
43225
43263
  const f3 = files[0];
43226
43264
  if (!f3) return;
43227
43265
  const reader = new FileReader();
43228
- reader.onload = () => handleChange(fieldName, {
43266
+ reader.onload = () => handleChange(fieldName2, {
43229
43267
  name: f3.name,
43230
43268
  mimeType: f3.type,
43231
43269
  sizeBytes: f3.size,
@@ -43242,7 +43280,7 @@ var init_Form = __esm({
43242
43280
  ...commonProps,
43243
43281
  type: "date",
43244
43282
  value: formatDateValue(currentValue),
43245
- onChange: (e) => handleChange(fieldName, e.target.value)
43283
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43246
43284
  }
43247
43285
  );
43248
43286
  case "datetime-local":
@@ -43252,7 +43290,7 @@ var init_Form = __esm({
43252
43290
  ...commonProps,
43253
43291
  type: "datetime-local",
43254
43292
  value: formatDateTimeValue(currentValue),
43255
- onChange: (e) => handleChange(fieldName, e.target.value)
43293
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43256
43294
  }
43257
43295
  );
43258
43296
  case "email":
@@ -43262,7 +43300,7 @@ var init_Form = __esm({
43262
43300
  ...commonProps,
43263
43301
  type: "email",
43264
43302
  value: String(currentValue),
43265
- onChange: (e) => handleChange(fieldName, e.target.value),
43303
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43266
43304
  minLength: field.min,
43267
43305
  maxLength: field.max
43268
43306
  }
@@ -43274,7 +43312,7 @@ var init_Form = __esm({
43274
43312
  ...commonProps,
43275
43313
  type: "url",
43276
43314
  value: String(currentValue),
43277
- onChange: (e) => handleChange(fieldName, e.target.value),
43315
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43278
43316
  minLength: field.min,
43279
43317
  maxLength: field.max
43280
43318
  }
@@ -43286,7 +43324,7 @@ var init_Form = __esm({
43286
43324
  ...commonProps,
43287
43325
  type: "password",
43288
43326
  value: String(currentValue),
43289
- onChange: (e) => handleChange(fieldName, e.target.value),
43327
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43290
43328
  minLength: field.min,
43291
43329
  maxLength: field.max
43292
43330
  }
@@ -43299,7 +43337,7 @@ var init_Form = __esm({
43299
43337
  ...commonProps,
43300
43338
  type: "text",
43301
43339
  value: String(currentValue),
43302
- onChange: (e) => handleChange(fieldName, e.target.value),
43340
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43303
43341
  minLength: field.min,
43304
43342
  maxLength: field.max
43305
43343
  }
@@ -43656,7 +43694,7 @@ function entityFieldsFromListItem(item) {
43656
43694
  }
43657
43695
  return result;
43658
43696
  }
43659
- function getStatusStyle(fieldName, value) {
43697
+ function getStatusStyle(fieldName2, value) {
43660
43698
  const val = String(value).toLowerCase();
43661
43699
  if (val.includes("complete") || val.includes("done"))
43662
43700
  return STATUS_STYLES.complete;
@@ -43672,12 +43710,12 @@ function getStatusStyle(fieldName, value) {
43672
43710
  if (val.includes("low")) return STATUS_STYLES.low;
43673
43711
  return STATUS_STYLES.default;
43674
43712
  }
43675
- function formatValue3(value, fieldName) {
43713
+ function formatValue3(value, fieldName2) {
43676
43714
  if (typeof value === "number") {
43677
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
43715
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
43678
43716
  return `${value}%`;
43679
43717
  }
43680
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
43718
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
43681
43719
  return new Intl.NumberFormat("en-US", {
43682
43720
  style: "currency",
43683
43721
  currency: "USD",
@@ -43700,8 +43738,8 @@ function formatValue3(value, fieldName) {
43700
43738
  }
43701
43739
  return String(value);
43702
43740
  }
43703
- function formatFieldLabel2(fieldName) {
43704
- return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
43741
+ function formatFieldLabel2(fieldName2) {
43742
+ return humanizeFieldName(fieldName2).replace(/\sId$/, "").trim();
43705
43743
  }
43706
43744
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
43707
43745
  var init_List = __esm({
@@ -43769,9 +43807,9 @@ var init_List = __esm({
43769
43807
  };
43770
43808
  StatusBadge = ({
43771
43809
  value,
43772
- fieldName
43810
+ fieldName: fieldName2
43773
43811
  }) => {
43774
- const style = getStatusStyle(fieldName, value);
43812
+ const style = getStatusStyle(fieldName2, value);
43775
43813
  return /* @__PURE__ */ jsxRuntime.jsxs(
43776
43814
  Typography,
43777
43815
  {
@@ -46703,8 +46741,8 @@ var init_StatCard = __esm({
46703
46741
  return items.length;
46704
46742
  }
46705
46743
  if (field.includes(":")) {
46706
- const [fieldName, fieldValue] = field.split(":");
46707
- return items.filter((item) => item[fieldName] === fieldValue).length;
46744
+ const [fieldName2, fieldValue] = field.split(":");
46745
+ return items.filter((item) => item[fieldName2] === fieldValue).length;
46708
46746
  }
46709
46747
  const fieldExistsOnItems = items.some((item) => field in item);
46710
46748
  if (fieldExistsOnItems) {
@@ -48224,6 +48262,8 @@ var init_component_registry_generated = __esm({
48224
48262
  init_DocSearch();
48225
48263
  init_DocSidebar();
48226
48264
  init_DocTOC();
48265
+ init_DocumentDetails();
48266
+ init_DocumentPanel();
48227
48267
  init_DocumentViewer();
48228
48268
  init_DrawFxLayer();
48229
48269
  init_DrawGroup();
@@ -48333,7 +48373,7 @@ var init_component_registry_generated = __esm({
48333
48373
  init_RelationSelect();
48334
48374
  init_RepeatableFormSection();
48335
48375
  init_ReplyTree();
48336
- init_RichBlockEditor();
48376
+ init_RichTextEditor();
48337
48377
  init_RuntimeDebugger2();
48338
48378
  init_ScaledDiagram();
48339
48379
  init_ScoreDisplay();
@@ -48496,6 +48536,8 @@ var init_component_registry_generated = __esm({
48496
48536
  "DocSearch": DocSearch,
48497
48537
  "DocSidebar": DocSidebar,
48498
48538
  "DocTOC": DocTOC,
48539
+ "DocumentDetails": DocumentDetails,
48540
+ "DocumentPanel": DocumentPanel,
48499
48541
  "DocumentViewer": DocumentViewer,
48500
48542
  "DrawFxLayer": DrawFxLayer,
48501
48543
  "DrawGroup": DrawGroup,
@@ -48607,7 +48649,7 @@ var init_component_registry_generated = __esm({
48607
48649
  "RelationSelect": RelationSelect,
48608
48650
  "RepeatableFormSection": RepeatableFormSection,
48609
48651
  "ReplyTree": ReplyTree,
48610
- "RichBlockEditor": RichBlockEditor,
48652
+ "RichTextEditor": RichTextEditor,
48611
48653
  "RuntimeDebugger": RuntimeDebugger,
48612
48654
  "ScaledDiagram": ScaledDiagram,
48613
48655
  "ScoreDisplay": ScoreDisplay,
@@ -48761,10 +48803,10 @@ function enrichFormFields(fields, entityDef) {
48761
48803
  }
48762
48804
  if (field && typeof field === "object" && !Array.isArray(field) && !React85__namespace.default.isValidElement(field) && !(field instanceof Date)) {
48763
48805
  const obj = field;
48764
- const fieldName = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
48765
- if (!fieldName) return field;
48806
+ const fieldName2 = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
48807
+ if (!fieldName2) return field;
48766
48808
  if (obj.type || obj.inputType) return field;
48767
- const entityField = fieldMap.get(fieldName);
48809
+ const entityField = fieldMap.get(fieldName2);
48768
48810
  if (!entityField) return field;
48769
48811
  const enriched = { ...obj, type: entityField.type };
48770
48812
  if (entityField.required && !("required" in obj)) {
@@ -48814,9 +48856,9 @@ function enrichDetailFields(fields, entityDef) {
48814
48856
  }
48815
48857
  if (field && typeof field === "object" && !Array.isArray(field) && !React85__namespace.default.isValidElement(field) && !(field instanceof Date)) {
48816
48858
  const obj = field;
48817
- const fieldName = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
48818
- if (!fieldName || obj.type) return field;
48819
- const meta = metaFor(fieldName);
48859
+ const fieldName2 = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
48860
+ if (!fieldName2 || obj.type) return field;
48861
+ const meta = metaFor(fieldName2);
48820
48862
  return meta ? { ...obj, ...meta } : field;
48821
48863
  }
48822
48864
  return field;
@@ -49655,9 +49697,9 @@ function UISlotRenderer({
49655
49697
  "ui-slot-renderer relative min-h-full",
49656
49698
  className
49657
49699
  ), children: [
49658
- /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex min-h-full", children: [
49659
- /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 shrink-0" }),
49660
- /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-h-[200px]" })
49700
+ /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "flex min-h-full flex-col lg:flex-row", children: [
49701
+ /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 lg:shrink-0" }),
49702
+ /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-w-0 min-h-[200px]" })
49661
49703
  ] }),
49662
49704
  /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "modal", portal: true }),
49663
49705
  /* @__PURE__ */ jsxRuntime.jsx(UISlotComponent, { slot: "drawer", portal: true }),
@@ -50180,7 +50222,7 @@ function buildTraitRenderConfig(binding, traitConfigsByName, payload) {
50180
50222
  }
50181
50223
  return void 0;
50182
50224
  }
50183
- function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
50225
+ function createSharedEntityWriter(binding, tick, traitStatesRef, emit, traitConfigsByName) {
50184
50226
  return (scratch) => {
50185
50227
  const traitName = binding.trait.name;
50186
50228
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -50189,9 +50231,13 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
50189
50231
  const writes = [];
50190
50232
  const ctx = evaluator.createMinimalContext(scratchEntity, {}, currentState);
50191
50233
  const declaredDefaults = runtime.collectDeclaredConfigDefaults(binding.trait);
50192
- const callSiteConfig = getBindingConfig(binding);
50193
- if (declaredDefaults || callSiteConfig) {
50194
- ctx.config = { ...declaredDefaults ?? {}, ...callSiteConfig ?? {} };
50234
+ const resolvedByName = traitConfigsByName?.[traitName];
50235
+ const callSiteRaw = getBindingConfig(binding);
50236
+ const callSiteConfig = callSiteRaw ? Object.fromEntries(
50237
+ Object.entries(callSiteRaw).filter(([, v]) => !containsConfigForward(v))
50238
+ ) : void 0;
50239
+ if (declaredDefaults || resolvedByName || callSiteConfig) {
50240
+ ctx.config = { ...declaredDefaults ?? {}, ...resolvedByName ?? {}, ...callSiteConfig ?? {} };
50195
50241
  }
50196
50242
  ctx.mutateEntity = (changes) => {
50197
50243
  for (const [field, value] of Object.entries(changes)) {
@@ -50794,7 +50840,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50794
50840
  const interval = entries[0].tick.interval;
50795
50841
  const onDue = timedTick(`tick:shared:${group.storeKey}@${String(interval)}`, () => {
50796
50842
  const writers = entries.map(
50797
- ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
50843
+ ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter, traitConfigsByName)
50798
50844
  );
50799
50845
  runTickFrame(group.storeKey, writers, sharedEntityStore);
50800
50846
  });