@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.
@@ -7,7 +7,7 @@ import { EventBusContext, useTraitScopeChain, useCurrentPagePath, useGameAudioCo
7
7
  export { GameAudioContext, GameAudioProvider, useGameAudioContext, useNavStack } from '@almadar/ui/providers';
8
8
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
9
9
  import * as LucideIcons2 from 'lucide-react';
10
- import { X, List, Printer, ChevronRight, ChevronLeft, Check, Copy, RotateCcw, Play, Terminal, CheckCircle, XCircle, ChevronDown, Search, ChevronUp, MoreHorizontal, ArrowLeft, DollarSign, Package, Calendar, Pencil, Eye, Image as Image$1, Upload, ZoomIn, TrendingUp, TrendingDown, Minus, AlertCircle, Circle, Clock, CheckCircle2, Loader2, Code, FileText, WrapText, HelpCircle, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, GitBranch, Plus, ArrowRight, Trash, Menu as Menu$1, AlertTriangle, Trash2, Eraser, ZoomOut, Download, Lightbulb, PauseCircle, Link2 } from 'lucide-react';
10
+ import { X, List, Printer, ChevronRight, ChevronLeft, Check, Copy, RotateCcw, Play, Terminal, CheckCircle, XCircle, ChevronDown, Search, ChevronUp, MoreHorizontal, ArrowLeft, DollarSign, Package, Calendar, Pencil, Eye, Image as Image$1, Upload, ZoomIn, TrendingUp, TrendingDown, Minus, AlertCircle, Circle, Clock, CheckCircle2, Loader2, Code, FileText, WrapText, HelpCircle, Bold, Italic, Underline, Strikethrough, Heading1, Heading2, Heading3, ListOrdered, Quote, Link as Link$1, GitBranch, Plus, ArrowRight, Trash, Menu as Menu$1, AlertTriangle, Trash2, Eraser, ZoomOut, Download, Lightbulb, PauseCircle, Link2 } from 'lucide-react';
11
11
  import { useTranslate } from '@almadar/ui/hooks';
12
12
  import { useUISlots, useTheme } from '@almadar/ui/context';
13
13
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
@@ -23950,8 +23950,9 @@ function DataGrid({
23950
23950
  const titleField = fieldDefs.find((f3) => f3.variant === "h3" || f3.variant === "h4") ?? fieldDefs[0];
23951
23951
  const badgeFields = fieldDefs.filter((f3) => f3.variant === "badge" && f3 !== titleField);
23952
23952
  const bodyFields = fieldDefs.filter((f3) => f3 !== titleField && !badgeFields.includes(f3));
23953
- const primaryActions = actionDefs.filter((a) => a.variant !== "danger");
23954
- const dangerActions = actionDefs.filter((a) => a.variant === "danger");
23953
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
23954
+ const inlineCardActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
23955
+ const menuCardActions = actionDefs.filter((a) => !inlineCardActions.includes(a));
23955
23956
  const fireAction = (action, itemData) => {
23956
23957
  if (action.navigatesTo) {
23957
23958
  const url = action.navigatesTo.replace(
@@ -23971,6 +23972,9 @@ function DataGrid({
23971
23972
  e.stopPropagation();
23972
23973
  fireAction(action, itemData);
23973
23974
  };
23975
+ const cardClickAction = actionDefs.find((a) => a.variant !== "danger");
23976
+ const handleCardClick = cardClickAction ? (itemData) => () => fireAction(cardClickAction, itemData) : void 0;
23977
+ const stopCardClick = handleCardClick ? (e) => e.stopPropagation() : void 0;
23974
23978
  const hasRenderProp = typeof children === "function";
23975
23979
  useEffect(() => {
23976
23980
  if (data.length > 0 && !hasRenderProp && fieldDefs.length === 0) {
@@ -24042,55 +24046,40 @@ function DataGrid({
24042
24046
  {
24043
24047
  "data-entity-row": true,
24044
24048
  "data-entity-id": id,
24045
- className: cn("relative group/rowactions", isSelected && "ring-2 ring-primary rounded-lg"),
24049
+ onClick: handleCardClick?.(itemData),
24050
+ className: cn("relative group/rowactions", handleCardClick && "cursor-pointer", isSelected && "ring-2 ring-primary rounded-lg"),
24046
24051
  children: [
24047
24052
  children(itemData, index),
24048
- actionDefs.length > 0 && /* @__PURE__ */ jsxs(Box, { className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
24049
- /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: [
24050
- (maxInlineActions != null ? actionDefs.slice(0, maxInlineActions) : actionDefs).map((action, idx) => /* @__PURE__ */ jsxs(
24051
- Button,
24052
- {
24053
- variant: action.variant === "primary" ? "primary" : "ghost",
24054
- size: "sm",
24055
- onClick: handleActionClick(action, itemData),
24056
- "data-testid": `action-${action.event}`,
24057
- "data-row-id": String(itemData.id),
24058
- className: action.variant === "danger" ? "text-error hover:text-error hover:bg-error/10" : void 0,
24059
- children: [
24060
- action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
24061
- action.label
24062
- ]
24063
- },
24064
- idx
24065
- )),
24066
- maxInlineActions != null && actionDefs.length > maxInlineActions && /* @__PURE__ */ jsx(
24067
- Menu,
24068
- {
24069
- position: "bottom-end",
24070
- trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
24071
- items: actionDefs.slice(maxInlineActions).map((action) => ({
24072
- label: action.label,
24073
- icon: action.icon,
24074
- event: action.event,
24075
- onClick: () => fireAction(action, itemData)
24076
- }))
24077
- }
24078
- )
24079
- ] }),
24080
- /* @__PURE__ */ jsx(Box, { className: "hidden [@media(pointer:coarse)]:block rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: /* @__PURE__ */ jsx(
24053
+ actionDefs.length > 0 && /* @__PURE__ */ jsx(Box, { onClick: stopCardClick, className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: [
24054
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxs(
24055
+ Button,
24056
+ {
24057
+ variant: "primary",
24058
+ size: "sm",
24059
+ onClick: handleActionClick(action, itemData),
24060
+ "data-testid": `action-${action.event}`,
24061
+ "data-row-id": String(itemData.id),
24062
+ children: [
24063
+ action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
24064
+ action.label
24065
+ ]
24066
+ },
24067
+ idx
24068
+ )),
24069
+ menuCardActions.length > 0 && /* @__PURE__ */ jsx(
24081
24070
  Menu,
24082
24071
  {
24083
24072
  position: "bottom-end",
24084
24073
  trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
24085
- items: actionDefs.map((action) => ({
24074
+ items: menuCardActions.map((action) => ({
24086
24075
  label: action.label,
24087
24076
  icon: action.icon,
24088
- event: action.event,
24077
+ variant: action.variant === "danger" ? "danger" : "default",
24089
24078
  onClick: () => fireAction(action, itemData)
24090
24079
  }))
24091
24080
  }
24092
- ) })
24093
- ] })
24081
+ )
24082
+ ] }) })
24094
24083
  ]
24095
24084
  },
24096
24085
  id
@@ -24104,12 +24093,14 @@ function DataGrid({
24104
24093
  {
24105
24094
  "data-entity-row": true,
24106
24095
  "data-entity-id": id,
24096
+ onClick: handleCardClick?.(itemData),
24107
24097
  className: cn(
24108
24098
  "bg-card rounded-lg",
24109
24099
  "border border-border",
24110
24100
  "shadow-elevation-card hover:shadow-elevation-dialog",
24111
24101
  "hover:border-primary transition-all",
24112
24102
  "flex flex-col",
24103
+ handleCardClick && "cursor-pointer",
24113
24104
  isSelected && "ring-2 ring-primary border-primary"
24114
24105
  ),
24115
24106
  children: [
@@ -24159,53 +24150,35 @@ function DataGrid({
24159
24150
  ] }, field.name);
24160
24151
  }) })
24161
24152
  ] }),
24162
- (primaryActions.length > 0 || dangerActions.length > 0) && /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
24163
- (maxInlineActions != null ? primaryActions.slice(0, maxInlineActions) : primaryActions).map((action, idx) => /* @__PURE__ */ jsx(
24153
+ actionDefs.length > 0 && /* @__PURE__ */ jsxs(HStack, { gap: "xs", onClick: stopCardClick, className: "flex-shrink-0", children: [
24154
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsx(
24164
24155
  Button,
24165
24156
  {
24166
- variant: action.variant === "primary" ? "primary" : "ghost",
24157
+ variant: "primary",
24167
24158
  size: "sm",
24168
24159
  onClick: handleActionClick(action, itemData),
24169
24160
  "data-testid": `action-${action.event}`,
24170
24161
  "data-row-id": String(itemData.id),
24171
24162
  "aria-label": action.label,
24172
24163
  title: action.label,
24173
- className: cn(
24174
- action.variant === "primary" ? void 0 : "text-muted-foreground hover:text-foreground",
24175
- action.icon && "px-2"
24176
- ),
24164
+ className: cn(action.icon && "px-2"),
24177
24165
  children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
24178
24166
  },
24179
24167
  idx
24180
24168
  )),
24181
- maxInlineActions != null && primaryActions.length > maxInlineActions && /* @__PURE__ */ jsx(
24169
+ menuCardActions.length > 0 && /* @__PURE__ */ jsx(
24182
24170
  Menu,
24183
24171
  {
24184
24172
  position: "bottom-end",
24185
24173
  trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
24186
- items: primaryActions.slice(maxInlineActions).map((action) => ({
24174
+ items: menuCardActions.map((action) => ({
24187
24175
  label: action.label,
24188
24176
  icon: action.icon,
24189
- event: action.event,
24177
+ variant: action.variant === "danger" ? "danger" : "default",
24190
24178
  onClick: () => fireAction(action, itemData)
24191
24179
  }))
24192
24180
  }
24193
- ),
24194
- dangerActions.map((action, idx) => /* @__PURE__ */ jsx(
24195
- Button,
24196
- {
24197
- variant: "ghost",
24198
- size: "sm",
24199
- onClick: handleActionClick(action, itemData),
24200
- "data-testid": `action-${action.event}`,
24201
- "data-row-id": String(itemData.id),
24202
- "aria-label": action.label,
24203
- title: action.label,
24204
- className: "text-error hover:text-error hover:bg-error/10 px-2",
24205
- children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
24206
- },
24207
- `danger-${idx}`
24208
- ))
24181
+ )
24209
24182
  ] })
24210
24183
  ] }) }),
24211
24184
  bodyFields.length > 0 && /* @__PURE__ */ jsx(Box, { className: "px-4 pt-2 pb-4 flex-1", children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
@@ -24226,8 +24199,8 @@ function DataGrid({
24226
24199
  }
24227
24200
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
24228
24201
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
24229
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
24230
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue(value, field.format) })
24202
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
24203
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
24231
24204
  ] }, field.name);
24232
24205
  }) })
24233
24206
  ] }) })
@@ -24447,54 +24420,59 @@ function DataList({
24447
24420
  if (!itemActions || itemActions.length === 0) return null;
24448
24421
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
24449
24422
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
24450
- return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
24451
- inline.map((action, idx) => /* @__PURE__ */ jsxs(
24452
- Button,
24453
- {
24454
- variant: action.variant ?? "ghost",
24455
- size: "sm",
24456
- onClick: handleActionClick(action, itemData),
24457
- "data-testid": `action-${action.event}`,
24458
- "data-row-id": String(itemData.id),
24459
- className: cn(
24460
- action.variant === "danger" && "text-error hover:bg-error/10",
24461
- // Must sit on the Button itself: the variant's own text colour
24462
- // beats an inherited one from the row wrapper.
24463
- onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
24464
- ),
24465
- children: [
24466
- action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
24467
- action.label
24468
- ]
24469
- },
24470
- idx
24471
- )),
24472
- overflow.length > 0 && /* @__PURE__ */ jsx(
24473
- Menu,
24474
- {
24475
- position: "bottom-end",
24476
- trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
24477
- items: overflow.map((action) => ({
24478
- label: action.label,
24479
- icon: action.icon,
24480
- event: action.event,
24481
- variant: action.variant === "danger" ? "danger" : "default",
24482
- onClick: () => eventBus.emit(`UI:${action.event}`, {
24483
- id: itemData.id,
24484
- row: itemData
24485
- })
24486
- }))
24487
- }
24488
- )
24489
- ] });
24423
+ return (
24424
+ // stopPropagation at the cluster: the "⋯" trigger opens the overflow
24425
+ // menu without also firing the row's default click (inline buttons
24426
+ // already stop it in handleActionClick; the Menu panel is portaled).
24427
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, className: "flex-shrink-0", children: [
24428
+ inline.map((action, idx) => /* @__PURE__ */ jsxs(
24429
+ Button,
24430
+ {
24431
+ variant: action.variant ?? "ghost",
24432
+ size: "sm",
24433
+ onClick: handleActionClick(action, itemData),
24434
+ "data-testid": `action-${action.event}`,
24435
+ "data-row-id": String(itemData.id),
24436
+ className: cn(
24437
+ action.variant === "danger" && "text-error hover:bg-error/10",
24438
+ // Must sit on the Button itself: the variant's own text colour
24439
+ // beats an inherited one from the row wrapper.
24440
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
24441
+ ),
24442
+ children: [
24443
+ action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
24444
+ action.label
24445
+ ]
24446
+ },
24447
+ idx
24448
+ )),
24449
+ overflow.length > 0 && /* @__PURE__ */ jsx(
24450
+ Menu,
24451
+ {
24452
+ position: "bottom-end",
24453
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
24454
+ items: overflow.map((action) => ({
24455
+ label: action.label,
24456
+ icon: action.icon,
24457
+ variant: action.variant === "danger" ? "danger" : "default",
24458
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
24459
+ id: itemData.id,
24460
+ row: itemData
24461
+ })
24462
+ }))
24463
+ }
24464
+ )
24465
+ ] })
24466
+ );
24490
24467
  };
24468
+ const rowClickEvent = itemClickEvent || itemActions?.find((a) => a.variant !== "danger")?.event;
24491
24469
  const handleRowClick = (itemData) => () => {
24492
- if (!itemClickEvent) return;
24470
+ if (!rowClickEvent) return;
24493
24471
  const payload = {
24494
24472
  id: itemData.id,
24495
24473
  row: itemData
24496
24474
  };
24497
- eventBus.emit(`UI:${itemClickEvent}`, payload);
24475
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
24498
24476
  };
24499
24477
  if (isLoading) {
24500
24478
  return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
@@ -24542,10 +24520,10 @@ function DataList({
24542
24520
  {
24543
24521
  "data-entity-row": true,
24544
24522
  "data-entity-id": id,
24545
- onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
24523
+ onClick: rowClickEvent ? handleRowClick(itemData) : void 0,
24546
24524
  className: cn(
24547
24525
  "flex px-4 group/rowactions",
24548
- itemClickEvent && "cursor-pointer",
24526
+ rowClickEvent && "cursor-pointer",
24549
24527
  isSent ? "justify-end" : "justify-start"
24550
24528
  ),
24551
24529
  children: /* @__PURE__ */ jsxs(
@@ -24621,7 +24599,7 @@ function DataList({
24621
24599
  const id2 = itemData.id || String(index);
24622
24600
  const actions = renderItemActions(itemData);
24623
24601
  return wrapDnd(
24624
- /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id2, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", itemClickEvent && "cursor-pointer"), children: [
24602
+ /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id2, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", rowClickEvent && "cursor-pointer"), children: [
24625
24603
  children(itemData, index),
24626
24604
  actions && /* @__PURE__ */ jsxs(Box, { className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
24627
24605
  /* @__PURE__ */ jsx(Box, { className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: actions }),
@@ -24633,7 +24611,6 @@ function DataList({
24633
24611
  items: (itemActions ?? []).map((action) => ({
24634
24612
  label: action.label,
24635
24613
  icon: action.icon,
24636
- event: action.event,
24637
24614
  variant: action.variant === "danger" ? "danger" : "default",
24638
24615
  onClick: () => eventBus.emit(`UI:${action.event}`, {
24639
24616
  id: itemData.id,
@@ -24650,12 +24627,16 @@ function DataList({
24650
24627
  const id = itemData.id || String(index);
24651
24628
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
24652
24629
  return wrapDnd(
24653
- /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn(itemClickEvent && "cursor-pointer"), children: [
24630
+ /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
24654
24631
  /* @__PURE__ */ jsxs(
24655
24632
  Box,
24656
24633
  {
24657
24634
  className: cn(
24658
- "group flex items-center gap-4 transition-all duration-fast",
24635
+ // items-start, not items-center: a multi-line row (title + meta +
24636
+ // progress) centred its action cluster in the vertical middle, so
24637
+ // the buttons floated between the meta fields instead of anchoring
24638
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
24639
+ "group flex items-start gap-4 transition-all duration-fast",
24659
24640
  isCompact ? "px-4 py-2" : "px-6 py-4",
24660
24641
  "hover:bg-muted/80",
24661
24642
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -24686,11 +24667,19 @@ function DataList({
24686
24667
  if (value === void 0 || value === null || value === "") return null;
24687
24668
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
24688
24669
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
24689
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
24690
- field.label ?? fieldLabel3(field.name),
24691
- ":"
24692
- ] }),
24693
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
24670
+ /* @__PURE__ */ jsxs(
24671
+ Typography,
24672
+ {
24673
+ variant: "caption",
24674
+ color: "secondary",
24675
+ className: cn(field.format !== "boolean" && "sr-only"),
24676
+ children: [
24677
+ field.label ?? fieldLabel3(field.name),
24678
+ ":"
24679
+ ]
24680
+ }
24681
+ ),
24682
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
24694
24683
  ] }, field.name);
24695
24684
  }) }),
24696
24685
  progressFields.map((field) => {
@@ -24709,7 +24698,7 @@ function DataList({
24709
24698
  ]
24710
24699
  }
24711
24700
  ),
24712
- isCard && !isLast && /* @__PURE__ */ jsx(Box, { className: "mx-6 border-b border-border/40" })
24701
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
24713
24702
  ] }, id)
24714
24703
  );
24715
24704
  };
@@ -24719,7 +24708,13 @@ function DataList({
24719
24708
  {
24720
24709
  className: cn(
24721
24710
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
24722
- !isCard && gapClass,
24711
+ // `gap-*` is inert on a block container, and Box only emits a display
24712
+ // class when its `display` prop is set — so every non-card list had
24713
+ // been asking for a gap that CSS silently dropped. flex-col makes it
24714
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
24715
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
24716
+ !isCard && "flex flex-col",
24717
+ !isCard && !isCompact && gapClass,
24723
24718
  listLookStyles[look],
24724
24719
  className
24725
24720
  ),
@@ -33259,7 +33254,7 @@ var init_MapView = __esm({
33259
33254
  shadowSize: [41, 41]
33260
33255
  });
33261
33256
  L.Marker.prototype.options.icon = defaultIcon;
33262
- const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback108, useState: useState104 } = React77__default;
33257
+ const { useEffect: useEffect69, useRef: useRef65, useCallback: useCallback108, useState: useState106 } = React77__default;
33263
33258
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
33264
33259
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
33265
33260
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -33304,7 +33299,7 @@ var init_MapView = __esm({
33304
33299
  showAttribution = true
33305
33300
  }) {
33306
33301
  const eventBus = useEventBus2();
33307
- const [clickedPosition, setClickedPosition] = useState104(null);
33302
+ const [clickedPosition, setClickedPosition] = useState106(null);
33308
33303
  const handleMapClick = useCallback108((lat, lng) => {
33309
33304
  if (showClickedPin) {
33310
33305
  setClickedPosition({ lat, lng });
@@ -37666,741 +37661,775 @@ var init_PositionedCanvas = __esm({
37666
37661
  PositionedCanvas.displayName = "PositionedCanvas";
37667
37662
  }
37668
37663
  });
37669
- function nextBlockId(prefix = "blk") {
37670
- _idSeq += 1;
37671
- const random = Math.random().toString(36).slice(2, 8);
37672
- return `${prefix}-${Date.now().toString(36)}-${_idSeq}-${random}`;
37673
- }
37674
- function normalizeBlocks(raw) {
37675
- if (!Array.isArray(raw) || raw.length === 0) return [createBlock("paragraph")];
37676
- return raw.map((row) => {
37677
- const entity = row;
37678
- const rawType = entity.type;
37679
- const type = typeof rawType === "string" && BLOCK_TYPES.has(rawType) ? rawType : "paragraph";
37680
- const id = typeof entity.id === "string" && entity.id ? entity.id : nextBlockId(type);
37681
- return { ...entity, id, type };
37682
- });
37683
- }
37684
- function createBlock(type) {
37685
- switch (type) {
37686
- case "bullet-list":
37687
- case "numbered-list":
37688
- return {
37689
- id: nextBlockId(type),
37690
- type,
37691
- children: [
37692
- { id: nextBlockId("li"), type: "paragraph", content: "" }
37693
- ]
37694
- };
37695
- case "image":
37696
- return {
37697
- id: nextBlockId(type),
37698
- type,
37699
- content: "",
37700
- metadata: { url: "", caption: "" }
37701
- };
37702
- case "code":
37703
- return {
37704
- id: nextBlockId(type),
37705
- type,
37706
- content: "",
37707
- metadata: { language: "plaintext" }
37708
- };
37709
- case "divider":
37710
- return { id: nextBlockId(type), type };
37711
- default:
37712
- return { id: nextBlockId(type), type, content: "" };
37664
+ function safeUrl(raw, kinds) {
37665
+ const url = raw.trim();
37666
+ if (url.startsWith("#") || url.startsWith("/")) return url;
37667
+ const lower = url.toLowerCase();
37668
+ for (const kind of kinds) {
37669
+ if (lower.startsWith(kind)) return url;
37713
37670
  }
37671
+ return null;
37714
37672
  }
37715
- function replaceBlock(blocks, id, updater) {
37716
- return blocks.map((block) => block.id === id ? updater(block) : block);
37717
- }
37718
- function removeBlock(blocks, id) {
37719
- return blocks.filter((block) => block.id !== id);
37720
- }
37721
- function duplicateBlock(block) {
37722
- return {
37723
- ...block,
37724
- id: nextBlockId(block.type),
37725
- children: block.children?.map((child) => ({
37726
- ...child,
37727
- id: nextBlockId("li")
37728
- })),
37729
- metadata: block.metadata ? { ...block.metadata } : void 0
37730
- };
37731
- }
37732
- function insertAfter(blocks, targetId, inserted) {
37733
- const idx = blocks.findIndex((b) => b.id === targetId);
37734
- if (idx === -1) return [...blocks, inserted];
37735
- const next = blocks.slice();
37736
- next.splice(idx + 1, 0, inserted);
37737
- return next;
37738
- }
37739
- function changeBlockType(block, type) {
37740
- if (block.type === type) return block;
37741
- if (type === "bullet-list" || type === "numbered-list") {
37742
- if (block.children && block.children.length > 0) {
37743
- return { ...block, type };
37673
+ function sanitizeNode(el) {
37674
+ const children = Array.from(el.children);
37675
+ for (const child of children) {
37676
+ const tag = child.tagName.toLowerCase();
37677
+ if (DROP_TAGS.has(tag)) {
37678
+ child.remove();
37679
+ continue;
37744
37680
  }
37745
- const seed2 = block.content ?? "";
37746
- return {
37747
- id: block.id,
37748
- type,
37749
- children: [
37750
- { id: nextBlockId("li"), type: "paragraph", content: seed2 }
37751
- ]
37752
- };
37753
- }
37754
- if (type === "divider") {
37755
- return { id: block.id, type };
37681
+ const allowed = ALLOWED_ATTRS[tag];
37682
+ if (!allowed) {
37683
+ sanitizeNode(child);
37684
+ child.replaceWith(...Array.from(child.childNodes));
37685
+ continue;
37686
+ }
37687
+ for (const attr of Array.from(child.attributes)) {
37688
+ const name = attr.name.toLowerCase();
37689
+ if (!allowed.includes(name)) {
37690
+ child.removeAttribute(attr.name);
37691
+ continue;
37692
+ }
37693
+ if (name === "href") {
37694
+ const url = safeUrl(attr.value, ["http://", "https://", "mailto:"]);
37695
+ if (url === null) child.removeAttribute(attr.name);
37696
+ else child.setAttribute("href", url);
37697
+ }
37698
+ if (name === "src") {
37699
+ const url = safeUrl(attr.value, ["http://", "https://", "data:image/"]);
37700
+ if (url === null) child.remove();
37701
+ else child.setAttribute("src", url);
37702
+ }
37703
+ }
37704
+ sanitizeNode(child);
37756
37705
  }
37757
- if (type === "image") {
37758
- return {
37759
- id: block.id,
37760
- type,
37761
- content: "",
37762
- metadata: { url: "", caption: block.content ?? "" }
37763
- };
37706
+ }
37707
+ function sanitizeRichHtml(html) {
37708
+ if (!html || typeof html !== "string") return "";
37709
+ if (typeof window === "undefined" || typeof DOMParser === "undefined") return "";
37710
+ const doc = new DOMParser().parseFromString(html, "text/html");
37711
+ sanitizeNode(doc.body);
37712
+ if (!doc.body.querySelector("img, hr") && (doc.body.textContent ?? "").trim().length === 0) {
37713
+ return "";
37764
37714
  }
37765
- if (type === "code") {
37766
- return {
37767
- id: block.id,
37768
- type,
37769
- content: block.content ?? "",
37770
- metadata: { language: "plaintext" }
37771
- };
37715
+ return doc.body.innerHTML.replace(/​/g, "");
37716
+ }
37717
+ function htmlIsEmpty(el) {
37718
+ if (el.querySelector("img, hr")) return false;
37719
+ return (el.textContent ?? "").trim().length === 0;
37720
+ }
37721
+ function closestBlock(node, root) {
37722
+ let cur = node;
37723
+ while (cur && cur !== root) {
37724
+ if (cur instanceof Element && BLOCK_TAGS.has(cur.tagName)) return cur;
37725
+ cur = cur.parentNode;
37772
37726
  }
37773
- const seed = block.children?.[0]?.content ?? block.content ?? "";
37774
- return { id: block.id, type, content: seed };
37727
+ return null;
37775
37728
  }
37776
- function BlockMenu({ block, readOnly, onDelete, onDuplicate, onChangeType }) {
37777
- const { t } = useTranslate();
37778
- const [open, setOpen] = useState(false);
37779
- const ref = useRef(null);
37780
- useEffect(() => {
37781
- if (!open) return;
37782
- function onDocClick(e) {
37783
- if (ref.current && !ref.current.contains(e.target)) {
37784
- setOpen(false);
37729
+ function RichTextStyles() {
37730
+ return /* @__PURE__ */ jsx("style", { children: RICH_TEXT_CSS });
37731
+ }
37732
+ function readToolbarState(root) {
37733
+ const sel = window.getSelection();
37734
+ if (!sel || sel.rangeCount === 0) return IDLE_TOOLBAR;
37735
+ const anchor = sel.anchorNode;
37736
+ if (!anchor || !root.contains(anchor)) return IDLE_TOOLBAR;
37737
+ let block = null;
37738
+ let link = false;
37739
+ let node = anchor;
37740
+ while (node && node !== root) {
37741
+ if (node instanceof Element) {
37742
+ const tag = node.tagName.toLowerCase();
37743
+ if (!block && (tag === "h1" || tag === "h2" || tag === "h3" || tag === "blockquote" || tag === "pre")) {
37744
+ block = tag;
37785
37745
  }
37746
+ if (tag === "a") link = true;
37786
37747
  }
37787
- document.addEventListener("mousedown", onDocClick);
37788
- return () => document.removeEventListener("mousedown", onDocClick);
37789
- }, [open]);
37790
- if (readOnly) return null;
37791
- return /* @__PURE__ */ jsxs(Box, { ref, className: "relative", children: [
37792
- /* @__PURE__ */ jsx(
37793
- Button,
37794
- {
37795
- type: "button",
37796
- variant: "ghost",
37797
- "aria-label": t("richBlockEditor.blockActions"),
37798
- className: cn(
37799
- "inline-flex items-center justify-center",
37800
- "h-6 w-6 rounded-sm p-0 gap-0",
37801
- "text-muted-foreground hover:bg-muted hover:text-foreground",
37802
- "opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
37803
- "transition-opacity"
37804
- ),
37805
- onClick: () => setOpen((v) => !v),
37806
- children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", className: "w-3.5 h-3.5" })
37807
- }
37808
- ),
37809
- open && /* @__PURE__ */ jsxs(
37810
- Box,
37811
- {
37812
- role: "menu",
37813
- className: cn(
37814
- "absolute right-0 z-10 mt-1 w-44",
37815
- "rounded-container border border-border bg-popover shadow-elevation-popover",
37816
- "py-1 text-sm"
37817
- ),
37818
- children: [
37819
- /* @__PURE__ */ jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t(BLOCK_TYPE_LABEL_KEY[block.type]) }),
37820
- /* @__PURE__ */ jsxs(
37821
- Button,
37822
- {
37823
- type: "button",
37824
- variant: "ghost",
37825
- role: "menuitem",
37826
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
37827
- onClick: () => {
37828
- onDuplicate();
37829
- setOpen(false);
37830
- },
37831
- children: [
37832
- /* @__PURE__ */ jsx(Icon, { name: "plus", className: "w-3.5 h-3.5" }),
37833
- " ",
37834
- t("richBlockEditor.duplicate")
37835
- ]
37836
- }
37837
- ),
37838
- /* @__PURE__ */ jsxs(
37839
- Button,
37840
- {
37841
- type: "button",
37842
- variant: "ghost",
37843
- role: "menuitem",
37844
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-destructive hover:bg-muted justify-start rounded-none",
37845
- onClick: () => {
37846
- onDelete();
37847
- setOpen(false);
37848
- },
37849
- children: [
37850
- /* @__PURE__ */ jsx(Icon, { name: "trash", className: "w-3.5 h-3.5" }),
37851
- " ",
37852
- t("common.delete")
37853
- ]
37854
- }
37855
- ),
37856
- CHANGEABLE_TYPES.includes(block.type) && /* @__PURE__ */ jsxs(Fragment, { children: [
37857
- /* @__PURE__ */ jsx(Box, { className: "my-1 border-t border-border" }),
37858
- /* @__PURE__ */ jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t("richBlockEditor.turnInto") }),
37859
- CHANGEABLE_TYPES.filter((bt) => bt !== block.type).map((bt) => /* @__PURE__ */ jsx(
37860
- Button,
37861
- {
37862
- type: "button",
37863
- variant: "ghost",
37864
- role: "menuitem",
37865
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
37866
- onClick: () => {
37867
- onChangeType(bt);
37868
- setOpen(false);
37869
- },
37870
- children: t(BLOCK_TYPE_LABEL_KEY[bt])
37871
- },
37872
- bt
37873
- ))
37874
- ] })
37875
- ]
37876
- }
37877
- )
37878
- ] });
37748
+ node = node.parentNode;
37749
+ }
37750
+ return {
37751
+ bold: document.queryCommandState("bold"),
37752
+ italic: document.queryCommandState("italic"),
37753
+ underline: document.queryCommandState("underline"),
37754
+ strikeThrough: document.queryCommandState("strikeThrough"),
37755
+ block,
37756
+ bullets: document.queryCommandState("insertUnorderedList"),
37757
+ numbers: document.queryCommandState("insertOrderedList"),
37758
+ link
37759
+ };
37879
37760
  }
37880
- function Editable({
37881
- tag,
37882
- value,
37883
- readOnly,
37884
- placeholder,
37885
- className,
37886
- ariaLabel,
37887
- onValueChange
37888
- }) {
37889
- const ref = useRef(null);
37890
- useEffect(() => {
37891
- const el = ref.current;
37892
- if (!el) return;
37893
- const isFocused = document.activeElement === el;
37894
- if (!isFocused && el.textContent !== value) {
37895
- el.textContent = value;
37896
- }
37897
- }, [value]);
37898
- const handleInput = useCallback(
37899
- (e) => {
37900
- onValueChange(e.currentTarget.textContent ?? "");
37901
- },
37902
- [onValueChange]
37903
- );
37761
+ function ToolbarButton({ icon: IconCmp, label, active, onExec }) {
37904
37762
  return /* @__PURE__ */ jsx(
37905
- Box,
37763
+ Button,
37906
37764
  {
37907
- as: tag,
37908
- ref,
37909
- contentEditable: !readOnly,
37910
- suppressContentEditableWarning: true,
37911
- role: readOnly ? void 0 : "textbox",
37912
- "aria-label": ariaLabel,
37913
- "aria-multiline": "true",
37914
- "data-placeholder": placeholder,
37765
+ type: "button",
37766
+ variant: "ghost",
37767
+ size: "sm",
37768
+ "aria-label": label,
37769
+ "aria-pressed": active,
37770
+ title: label,
37915
37771
  className: cn(
37916
- "outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-sm",
37917
- "empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground/60",
37918
- className
37772
+ "h-8 w-8 p-0 gap-0 justify-center",
37773
+ active && "bg-muted text-foreground"
37919
37774
  ),
37920
- onInput: handleInput
37775
+ onMouseDown: (e) => {
37776
+ e.preventDefault();
37777
+ onExec();
37778
+ },
37779
+ children: /* @__PURE__ */ jsx(IconCmp, { size: 15 })
37921
37780
  }
37922
37781
  );
37923
37782
  }
37924
- function BlockRow({
37925
- block,
37926
- readOnly,
37927
- showAffordances,
37928
- placeholder,
37929
- onUpdate,
37930
- onDelete,
37931
- onDuplicate,
37932
- onChangeType
37933
- }) {
37934
- const { t } = useTranslate();
37935
- const setContent = useCallback(
37936
- (next) => onUpdate((b) => ({ ...b, content: next })),
37937
- [onUpdate]
37938
- );
37939
- const setMetadata = useCallback(
37940
- (key, value) => onUpdate((b) => ({
37941
- ...b,
37942
- metadata: { ...b.metadata ?? {}, [key]: value }
37943
- })),
37944
- [onUpdate]
37945
- );
37946
- const setChildContent = useCallback(
37947
- (childId, next) => onUpdate((b) => ({
37948
- ...b,
37949
- children: (b.children ?? []).map(
37950
- (c) => c.id === childId ? { ...c, content: next } : c
37951
- )
37952
- })),
37953
- [onUpdate]
37954
- );
37955
- const addListItem = useCallback(
37956
- () => onUpdate((b) => ({
37957
- ...b,
37958
- children: [
37959
- ...b.children ?? [],
37960
- { id: nextBlockId("li"), type: "paragraph", content: "" }
37961
- ]
37962
- })),
37963
- [onUpdate]
37964
- );
37965
- const removeListItem = useCallback(
37966
- (childId) => onUpdate((b) => {
37967
- const remaining = (b.children ?? []).filter((c) => c.id !== childId);
37968
- return {
37969
- ...b,
37970
- children: remaining.length === 0 ? [{ id: nextBlockId("li"), type: "paragraph", content: "" }] : remaining
37971
- };
37972
- }),
37973
- [onUpdate]
37974
- );
37975
- const renderBody = () => {
37976
- switch (block.type) {
37977
- case "heading-1":
37978
- return /* @__PURE__ */ jsx(
37979
- Editable,
37980
- {
37981
- tag: "h1",
37982
- value: block.content ?? "",
37983
- readOnly,
37984
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading1"),
37985
- ariaLabel: t("richBlockEditor.aria.heading1Block"),
37986
- className: "text-3xl font-bold leading-tight",
37987
- onValueChange: setContent
37988
- }
37989
- );
37990
- case "heading-2":
37991
- return /* @__PURE__ */ jsx(
37992
- Editable,
37993
- {
37994
- tag: "h2",
37995
- value: block.content ?? "",
37996
- readOnly,
37997
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading2"),
37998
- ariaLabel: t("richBlockEditor.aria.heading2Block"),
37999
- className: "text-2xl font-semibold leading-tight",
38000
- onValueChange: setContent
38001
- }
38002
- );
38003
- case "heading-3":
38004
- return /* @__PURE__ */ jsx(
38005
- Editable,
38006
- {
38007
- tag: "h3",
38008
- value: block.content ?? "",
38009
- readOnly,
38010
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading3"),
38011
- ariaLabel: t("richBlockEditor.aria.heading3Block"),
38012
- className: "text-xl font-semibold leading-tight",
38013
- onValueChange: setContent
38014
- }
38015
- );
38016
- case "quote":
38017
- return /* @__PURE__ */ jsx(
38018
- Editable,
38019
- {
38020
- tag: "blockquote",
38021
- value: block.content ?? "",
38022
- readOnly,
38023
- placeholder: placeholder ?? t("richBlockEditor.placeholder.quote"),
38024
- ariaLabel: t("richBlockEditor.aria.quoteBlock"),
38025
- className: "border-l-4 border-primary/60 pl-4 italic text-muted-foreground",
38026
- onValueChange: setContent
38027
- }
38028
- );
38029
- case "code":
38030
- return /* @__PURE__ */ jsxs(Box, { className: "rounded-md border border-border bg-muted/40", children: [
38031
- /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between border-b border-border px-3 py-1 text-xs text-muted-foreground", children: [
38032
- /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "uppercase tracking-wide", children: t("richBlockEditor.blockType.code") }),
38033
- !readOnly && /* @__PURE__ */ jsx(
38034
- Input,
38035
- {
38036
- inputType: "text",
38037
- value: String(block.metadata?.language ?? "plaintext"),
38038
- "aria-label": t("richBlockEditor.aria.codeLanguage"),
38039
- className: cn(
38040
- "h-6 w-32 rounded-sm border border-border bg-background",
38041
- "px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
38042
- ),
38043
- onChange: (e) => setMetadata("language", e.target.value)
38044
- }
38045
- ),
38046
- readOnly && /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "text-xs", children: String(block.metadata?.language ?? "plaintext") })
38047
- ] }),
38048
- /* @__PURE__ */ jsx(
38049
- Editable,
38050
- {
38051
- tag: "pre",
38052
- value: block.content ?? "",
38053
- readOnly,
38054
- placeholder: placeholder ?? t("richBlockEditor.placeholder.code"),
38055
- ariaLabel: t("richBlockEditor.aria.codeBlock"),
38056
- className: "block whitespace-pre-wrap p-3 font-mono text-sm leading-relaxed",
38057
- onValueChange: setContent
38058
- }
38059
- )
38060
- ] });
38061
- case "divider":
38062
- return /* @__PURE__ */ jsx(Divider, { className: "my-2" });
38063
- case "image": {
38064
- const url = String(block.metadata?.url ?? "");
38065
- const caption = String(block.metadata?.caption ?? "");
38066
- const imgProps = {
38067
- src: url,
38068
- alt: caption || t("richBlockEditor.embeddedImage"),
38069
- className: "max-h-96 w-full rounded-md border border-border object-contain"
37783
+ var CHANGE_DEBOUNCE_MS, ALLOWED_ATTRS, DROP_TAGS, MD_BLOCK_PREFIXES, BLOCK_TAGS, MD_INLINE_RULES, RICH_TEXT_CSS, IDLE_TOOLBAR, RichTextEditor;
37784
+ var init_RichTextEditor = __esm({
37785
+ "components/core/molecules/RichTextEditor.tsx"() {
37786
+ "use client";
37787
+ init_cn();
37788
+ init_Box();
37789
+ init_Button();
37790
+ init_useEventBus();
37791
+ CHANGE_DEBOUNCE_MS = 400;
37792
+ ALLOWED_ATTRS = {
37793
+ p: [],
37794
+ h1: [],
37795
+ h2: [],
37796
+ h3: [],
37797
+ h4: [],
37798
+ ul: [],
37799
+ ol: [],
37800
+ li: [],
37801
+ blockquote: [],
37802
+ pre: [],
37803
+ code: [],
37804
+ b: [],
37805
+ strong: [],
37806
+ i: [],
37807
+ em: [],
37808
+ u: [],
37809
+ s: [],
37810
+ strike: [],
37811
+ br: [],
37812
+ hr: [],
37813
+ div: [],
37814
+ span: [],
37815
+ a: ["href"],
37816
+ img: ["src", "alt"]
37817
+ };
37818
+ DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "link", "meta", "form", "input", "button", "textarea", "select"]);
37819
+ MD_BLOCK_PREFIXES = {
37820
+ "#": { kind: "block", tag: "h1" },
37821
+ "##": { kind: "block", tag: "h2" },
37822
+ "###": { kind: "block", tag: "h3" },
37823
+ ">": { kind: "block", tag: "blockquote" },
37824
+ "```": { kind: "block", tag: "pre" },
37825
+ "-": { kind: "list", command: "insertUnorderedList" },
37826
+ "*": { kind: "list", command: "insertUnorderedList" },
37827
+ "1.": { kind: "list", command: "insertOrderedList" }
37828
+ };
37829
+ BLOCK_TAGS = /* @__PURE__ */ new Set(["P", "DIV", "LI", "H1", "H2", "H3", "H4", "BLOCKQUOTE", "PRE"]);
37830
+ MD_INLINE_RULES = [
37831
+ { pattern: /\*\*([^*\n]+)\*\*$/, wrap: "strong" },
37832
+ { pattern: /(^|[^*])\*([^*\n]+)\*$/, wrap: "em" },
37833
+ { pattern: /`([^`\n]+)`$/, wrap: "code" }
37834
+ ];
37835
+ RICH_TEXT_CSS = `
37836
+ .almadar-rich-text { line-height: 1.7; color: var(--color-foreground); }
37837
+ .almadar-rich-text h1 { font-size: 1.875rem; font-weight: 700; line-height: 1.25; margin: 1.25em 0 0.4em; }
37838
+ .almadar-rich-text h2 { font-size: 1.5rem; font-weight: 650; line-height: 1.3; margin: 1.1em 0 0.4em; }
37839
+ .almadar-rich-text h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.35; margin: 1em 0 0.35em; }
37840
+ .almadar-rich-text h1:first-child, .almadar-rich-text h2:first-child, .almadar-rich-text h3:first-child { margin-top: 0; }
37841
+ .almadar-rich-text p { margin: 0.5em 0; }
37842
+ .almadar-rich-text ul { list-style: disc; padding-inline-start: 1.5rem; margin: 0.5em 0; }
37843
+ .almadar-rich-text ol { list-style: decimal; padding-inline-start: 1.5rem; margin: 0.5em 0; }
37844
+ .almadar-rich-text li { margin: 0.25em 0; }
37845
+ .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; }
37846
+ .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; }
37847
+ .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; }
37848
+ .almadar-rich-text pre code { background: transparent; padding: 0; }
37849
+ .almadar-rich-text a { color: var(--color-primary); text-decoration: underline; }
37850
+ .almadar-rich-text hr { border: none; border-top: 1px solid var(--color-border); margin: 1.25em 0; }
37851
+ .almadar-rich-text img { max-width: 100%; border-radius: 0.375rem; }
37852
+ `;
37853
+ IDLE_TOOLBAR = {
37854
+ bold: false,
37855
+ italic: false,
37856
+ underline: false,
37857
+ strikeThrough: false,
37858
+ block: null,
37859
+ bullets: false,
37860
+ numbers: false,
37861
+ link: false
37862
+ };
37863
+ RichTextEditor = ({
37864
+ value,
37865
+ onChange,
37866
+ changeEvent,
37867
+ readOnly = false,
37868
+ placeholder,
37869
+ showToolbar = true,
37870
+ className
37871
+ }) => {
37872
+ const { t } = useTranslate();
37873
+ const ref = useRef(null);
37874
+ const [toolbar, setToolbar] = useState(IDLE_TOOLBAR);
37875
+ const [empty, setEmpty] = useState(() => !value || !sanitizeRichHtml(value).trim());
37876
+ const eventBus = useEventBus();
37877
+ const onChangeRef = useRef(onChange);
37878
+ const changeEventRef = useRef(changeEvent);
37879
+ useEffect(() => {
37880
+ onChangeRef.current = onChange;
37881
+ changeEventRef.current = changeEvent;
37882
+ }, [onChange, changeEvent]);
37883
+ const emitTimerRef = useRef(null);
37884
+ const emitNow = useCallback(() => {
37885
+ const el = ref.current;
37886
+ if (!el) return;
37887
+ const html = sanitizeRichHtml(el.innerHTML);
37888
+ onChangeRef.current?.(html);
37889
+ const evt = changeEventRef.current;
37890
+ if (evt) eventBus.emit(`UI:${evt}`, { value: html });
37891
+ }, [eventBus]);
37892
+ const scheduleEmit = useCallback(() => {
37893
+ if (emitTimerRef.current) clearTimeout(emitTimerRef.current);
37894
+ emitTimerRef.current = setTimeout(() => {
37895
+ emitTimerRef.current = null;
37896
+ emitNow();
37897
+ }, CHANGE_DEBOUNCE_MS);
37898
+ }, [emitNow]);
37899
+ const flushEmit = useCallback(() => {
37900
+ if (!emitTimerRef.current) return;
37901
+ clearTimeout(emitTimerRef.current);
37902
+ emitTimerRef.current = null;
37903
+ emitNow();
37904
+ }, [emitNow]);
37905
+ useEffect(() => () => {
37906
+ if (emitTimerRef.current) {
37907
+ clearTimeout(emitTimerRef.current);
37908
+ emitTimerRef.current = null;
37909
+ emitNow();
37910
+ }
37911
+ }, [emitNow]);
37912
+ useEffect(() => {
37913
+ if (readOnly) return;
37914
+ const el = ref.current;
37915
+ if (!el) return;
37916
+ if (document.activeElement === el) return;
37917
+ const next = sanitizeRichHtml(value ?? "") || "<p><br></p>";
37918
+ if (el.innerHTML !== next) {
37919
+ el.innerHTML = next;
37920
+ setEmpty(htmlIsEmpty(el));
37921
+ }
37922
+ }, [readOnly, value]);
37923
+ useEffect(() => {
37924
+ if (readOnly) return;
37925
+ document.execCommand("defaultParagraphSeparator", false, "p");
37926
+ }, [readOnly]);
37927
+ useEffect(() => {
37928
+ if (readOnly || !showToolbar) return;
37929
+ const onSelectionChange = () => {
37930
+ const el = ref.current;
37931
+ if (!el) return;
37932
+ setToolbar(readToolbarState(el));
38070
37933
  };
38071
- return /* @__PURE__ */ jsxs(Box, { className: "space-y-2", children: [
38072
- url ? /* @__PURE__ */ jsx(Box, { as: "img", ...imgProps }) : /* @__PURE__ */ jsxs(
38073
- Box,
38074
- {
38075
- className: cn(
38076
- "flex h-32 items-center justify-center",
38077
- "rounded-md border border-dashed border-border",
38078
- "text-sm text-muted-foreground"
38079
- ),
38080
- children: [
38081
- /* @__PURE__ */ jsx(Icon, { name: "image", className: "mr-2 w-4 h-4" }),
38082
- " ",
38083
- t("richBlockEditor.noImageUrl")
38084
- ]
38085
- }
38086
- ),
38087
- !readOnly && /* @__PURE__ */ jsxs(Box, { className: "flex flex-col gap-2 sm:flex-row", children: [
38088
- /* @__PURE__ */ jsx(
38089
- Input,
38090
- {
38091
- inputType: "url",
38092
- value: url,
38093
- placeholder: "https://example.com/image.png",
38094
- "aria-label": t("richBlockEditor.aria.imageUrl"),
38095
- className: cn(
38096
- "h-8 flex-1 rounded-sm border border-border bg-background",
38097
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
38098
- ),
38099
- onChange: (e) => setMetadata("url", e.target.value)
38100
- }
38101
- ),
38102
- /* @__PURE__ */ jsx(
38103
- Input,
38104
- {
38105
- inputType: "text",
38106
- value: caption,
38107
- placeholder: t("richBlockEditor.placeholder.caption"),
38108
- "aria-label": t("richBlockEditor.aria.imageCaption"),
38109
- className: cn(
38110
- "h-8 flex-1 rounded-sm border border-border bg-background",
38111
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
38112
- ),
38113
- onChange: (e) => setMetadata("caption", e.target.value)
38114
- }
38115
- )
38116
- ] }),
38117
- readOnly && caption && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-center text-muted-foreground", children: caption })
37934
+ document.addEventListener("selectionchange", onSelectionChange);
37935
+ return () => document.removeEventListener("selectionchange", onSelectionChange);
37936
+ }, [readOnly, showToolbar]);
37937
+ const tryInlineMarkdown = useCallback(() => {
37938
+ const root = ref.current;
37939
+ if (!root) return false;
37940
+ const sel = window.getSelection();
37941
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
37942
+ const node = sel.anchorNode;
37943
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
37944
+ if (node.parentElement?.closest("code, pre")) return false;
37945
+ const text = (node.textContent ?? "").slice(0, sel.anchorOffset);
37946
+ for (const rule of MD_INLINE_RULES) {
37947
+ const m = rule.pattern.exec(text);
37948
+ if (!m) continue;
37949
+ const inner = rule.wrap === "em" ? m[2] : m[1];
37950
+ if (!inner || !inner.trim()) continue;
37951
+ const lead = rule.wrap === "em" ? m[1]?.length ?? 0 : 0;
37952
+ const range = document.createRange();
37953
+ range.setStart(node, m.index + lead);
37954
+ range.setEnd(node, sel.anchorOffset);
37955
+ const el = document.createElement(rule.wrap);
37956
+ el.textContent = inner;
37957
+ range.deleteContents();
37958
+ range.insertNode(el);
37959
+ const after = document.createTextNode("\u200B");
37960
+ el.parentNode?.insertBefore(after, el.nextSibling);
37961
+ const caret = document.createRange();
37962
+ caret.setStart(after, 1);
37963
+ caret.collapse(true);
37964
+ sel.removeAllRanges();
37965
+ sel.addRange(caret);
37966
+ return true;
37967
+ }
37968
+ return false;
37969
+ }, []);
37970
+ const tryBlockMarkdown = useCallback(() => {
37971
+ const root = ref.current;
37972
+ if (!root) return false;
37973
+ const sel = window.getSelection();
37974
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
37975
+ const node = sel.anchorNode;
37976
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
37977
+ const block = closestBlock(node, root);
37978
+ if (block ? block.firstChild !== node : root.firstChild !== node) return false;
37979
+ const prefix = (node.textContent ?? "").slice(0, sel.anchorOffset);
37980
+ const rule = MD_BLOCK_PREFIXES[prefix];
37981
+ if (!rule) return false;
37982
+ if (node.parentElement?.closest("pre, code")) return false;
37983
+ if (rule.kind === "list" && node.parentElement?.closest("li")) return false;
37984
+ node.deleteData(0, prefix.length);
37985
+ const caret = document.createRange();
37986
+ caret.setStart(node, 0);
37987
+ caret.collapse(true);
37988
+ sel.removeAllRanges();
37989
+ sel.addRange(caret);
37990
+ if (rule.kind === "list") {
37991
+ const container = block ?? node.parentElement;
37992
+ const list = document.createElement(rule.command === "insertOrderedList" ? "ol" : "ul");
37993
+ const li = document.createElement("li");
37994
+ if (block) {
37995
+ while (block.firstChild) li.appendChild(block.firstChild);
37996
+ list.appendChild(li);
37997
+ block.replaceWith(list);
37998
+ } else if (container) {
37999
+ li.appendChild(node);
38000
+ list.appendChild(li);
38001
+ container.appendChild(list);
38002
+ }
38003
+ if (!li.firstChild) li.appendChild(document.createElement("br"));
38004
+ const liCaret = document.createRange();
38005
+ liCaret.selectNodeContents(li);
38006
+ liCaret.collapse(true);
38007
+ sel.removeAllRanges();
38008
+ sel.addRange(liCaret);
38009
+ } else {
38010
+ document.execCommand("formatBlock", false, rule.tag);
38011
+ }
38012
+ return true;
38013
+ }, []);
38014
+ const afterEdit = useCallback(() => {
38015
+ const el = ref.current;
38016
+ if (!el) return;
38017
+ tryInlineMarkdown();
38018
+ setEmpty(htmlIsEmpty(el));
38019
+ setToolbar(readToolbarState(el));
38020
+ scheduleEmit();
38021
+ }, [scheduleEmit, tryInlineMarkdown]);
38022
+ const handleKeyDown = useCallback((e) => {
38023
+ if (e.key === " " && tryBlockMarkdown()) {
38024
+ e.preventDefault();
38025
+ afterEdit();
38026
+ }
38027
+ }, [afterEdit, tryBlockMarkdown]);
38028
+ const execInline = useCallback((command) => {
38029
+ document.execCommand(command, false);
38030
+ afterEdit();
38031
+ }, [afterEdit]);
38032
+ const execBlock = useCallback((tag) => {
38033
+ const next = toolbar.block === tag ? "p" : tag;
38034
+ document.execCommand("formatBlock", false, next);
38035
+ afterEdit();
38036
+ }, [afterEdit, toolbar.block]);
38037
+ const execList = useCallback((command) => {
38038
+ document.execCommand(command, false);
38039
+ afterEdit();
38040
+ }, [afterEdit]);
38041
+ const execLink = useCallback(() => {
38042
+ if (toolbar.link) {
38043
+ document.execCommand("unlink", false);
38044
+ afterEdit();
38045
+ return;
38046
+ }
38047
+ const url = window.prompt(t("richTextEditor.linkPrompt"));
38048
+ if (!url) return;
38049
+ const safe = safeUrl(url, ["http://", "https://", "mailto:"]) ?? `https://${url}`;
38050
+ document.execCommand("createLink", false, safe);
38051
+ afterEdit();
38052
+ }, [afterEdit, t, toolbar.link]);
38053
+ const execRule = useCallback(() => {
38054
+ document.execCommand("insertHorizontalRule", false);
38055
+ afterEdit();
38056
+ }, [afterEdit]);
38057
+ if (readOnly) {
38058
+ return /* @__PURE__ */ jsxs(Box, { className: cn("almadar-rich-text max-w-none", className), children: [
38059
+ /* @__PURE__ */ jsx(RichTextStyles, {}),
38060
+ /* @__PURE__ */ jsx(Box, { dangerouslySetInnerHTML: { __html: sanitizeRichHtml(value ?? "") } })
38118
38061
  ] });
38119
38062
  }
38120
- case "bullet-list":
38121
- case "numbered-list": {
38122
- const items = block.children ?? [];
38123
- return /* @__PURE__ */ jsxs(
38063
+ return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
38064
+ /* @__PURE__ */ jsx(RichTextStyles, {}),
38065
+ showToolbar && /* @__PURE__ */ jsxs(
38124
38066
  Box,
38125
38067
  {
38126
- as: block.type === "bullet-list" ? "ul" : "ol",
38068
+ role: "toolbar",
38069
+ "aria-label": t("richTextEditor.editorToolbar"),
38127
38070
  className: cn(
38128
- "space-y-1 pl-6",
38129
- block.type === "bullet-list" ? "list-disc" : "list-decimal"
38071
+ "sticky top-0 z-10 flex flex-wrap items-center gap-0.5 self-start",
38072
+ "rounded-md border border-border bg-background/95 px-1 py-0.5 shadow-sm"
38130
38073
  ),
38131
38074
  children: [
38132
- items.map((child) => /* @__PURE__ */ jsxs(Box, { as: "li", className: "group/item flex items-start gap-2", children: [
38133
- /* @__PURE__ */ jsx(
38134
- Editable,
38135
- {
38136
- tag: "span",
38137
- value: child.content ?? "",
38138
- readOnly,
38139
- placeholder: t("richBlockEditor.placeholder.listItem"),
38140
- ariaLabel: t("richBlockEditor.aria.listItem"),
38141
- className: "inline-block min-w-[1ch] flex-1",
38142
- onValueChange: (next) => setChildContent(child.id, next)
38143
- }
38144
- ),
38145
- !readOnly && showAffordances && /* @__PURE__ */ jsx(
38146
- Button,
38147
- {
38148
- type: "button",
38149
- variant: "ghost",
38150
- "aria-label": t("richBlockEditor.aria.removeListItem"),
38151
- className: cn(
38152
- "h-5 w-5 shrink-0 rounded-sm text-muted-foreground p-0 gap-0",
38153
- "opacity-0 group-hover/item:opacity-100 hover:bg-muted hover:text-foreground"
38154
- ),
38155
- onClick: () => removeListItem(child.id),
38156
- children: /* @__PURE__ */ jsx(Icon, { name: "trash", className: "w-3 h-3" })
38157
- }
38158
- )
38159
- ] }, child.id)),
38160
- !readOnly && showAffordances && /* @__PURE__ */ jsx(Box, { as: "li", className: "list-none pl-0", children: /* @__PURE__ */ jsxs(
38161
- Button,
38162
- {
38163
- type: "button",
38164
- variant: "ghost",
38165
- className: cn(
38166
- "inline-flex items-center gap-1 text-xs text-muted-foreground",
38167
- "hover:text-foreground p-0 h-auto"
38168
- ),
38169
- onClick: addListItem,
38170
- children: [
38171
- /* @__PURE__ */ jsx(Icon, { name: "plus", className: "w-3 h-3" }),
38172
- " ",
38173
- t("richBlockEditor.addItem")
38174
- ]
38175
- }
38176
- ) })
38075
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Bold, label: t("richTextEditor.bold"), active: toolbar.bold, onExec: () => execInline("bold") }),
38076
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Italic, label: t("richTextEditor.italic"), active: toolbar.italic, onExec: () => execInline("italic") }),
38077
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Underline, label: t("richTextEditor.underline"), active: toolbar.underline, onExec: () => execInline("underline") }),
38078
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Strikethrough, label: t("richTextEditor.strikethrough"), active: toolbar.strikeThrough, onExec: () => execInline("strikeThrough") }),
38079
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
38080
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading1, label: t("richTextEditor.heading1"), active: toolbar.block === "h1", onExec: () => execBlock("h1") }),
38081
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading2, label: t("richTextEditor.heading2"), active: toolbar.block === "h2", onExec: () => execBlock("h2") }),
38082
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading3, label: t("richTextEditor.heading3"), active: toolbar.block === "h3", onExec: () => execBlock("h3") }),
38083
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
38084
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: List, label: t("richTextEditor.bulletList"), active: toolbar.bullets, onExec: () => execList("insertUnorderedList") }),
38085
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: ListOrdered, label: t("richTextEditor.numberedList"), active: toolbar.numbers, onExec: () => execList("insertOrderedList") }),
38086
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
38087
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
38088
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
38089
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Link$1, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
38090
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Minus, label: t("richTextEditor.divider"), onExec: execRule })
38177
38091
  ]
38178
38092
  }
38179
- );
38180
- }
38181
- case "paragraph":
38182
- default:
38183
- return /* @__PURE__ */ jsx(
38184
- Editable,
38093
+ ),
38094
+ /* @__PURE__ */ jsx(
38095
+ Box,
38185
38096
  {
38186
- tag: "p",
38187
- value: block.content ?? "",
38188
- readOnly,
38189
- placeholder: placeholder ?? t("richBlockEditor.placeholder.paragraph"),
38190
- ariaLabel: t("richBlockEditor.aria.paragraphBlock"),
38191
- className: "leading-7",
38192
- onValueChange: setContent
38097
+ ref,
38098
+ contentEditable: true,
38099
+ suppressContentEditableWarning: true,
38100
+ role: "textbox",
38101
+ "aria-multiline": "true",
38102
+ "aria-label": t("richTextEditor.editorSurface"),
38103
+ "data-placeholder": placeholder ?? t("richTextEditor.placeholder"),
38104
+ "data-empty": empty ? "true" : "false",
38105
+ className: cn(
38106
+ "almadar-rich-text max-w-none min-h-[8rem] outline-none",
38107
+ "data-[empty=true]:before:content-[attr(data-placeholder)]",
38108
+ "data-[empty=true]:before:text-muted-foreground/60",
38109
+ "data-[empty=true]:before:pointer-events-none",
38110
+ "data-[empty=true]:before:absolute",
38111
+ "relative cursor-text"
38112
+ ),
38113
+ onInput: afterEdit,
38114
+ onKeyDown: handleKeyDown,
38115
+ onBlur: flushEmit
38193
38116
  }
38194
- );
38195
- }
38117
+ )
38118
+ ] });
38119
+ };
38120
+ RichTextEditor.displayName = "RichTextEditor";
38121
+ }
38122
+ });
38123
+ function renderIconInput4(icon, props) {
38124
+ return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
38125
+ }
38126
+ function DocumentPanel({
38127
+ entity,
38128
+ title,
38129
+ subtitle,
38130
+ value,
38131
+ actions,
38132
+ maxInlineActions,
38133
+ editing = false,
38134
+ titleCommitEvent,
38135
+ contentChangeEvent,
38136
+ editEvent,
38137
+ doneEvent,
38138
+ editLabel,
38139
+ doneLabel,
38140
+ autosaveHint,
38141
+ placeholder,
38142
+ className
38143
+ }) {
38144
+ const eventBus = useEventBus();
38145
+ const { t } = useTranslate();
38146
+ const [titleDraft, setTitleDraft] = useState(null);
38147
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
38148
+ const actionDefs = actions ?? [];
38149
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
38150
+ const inlineActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
38151
+ const menuActions = actionDefs.filter((a) => !inlineActions.includes(a));
38152
+ const fireAction = (action) => {
38153
+ if (!action.event) return;
38154
+ eventBus.emit(`UI:${action.event}`, { id: recordId, row: entity });
38196
38155
  };
38197
- return /* @__PURE__ */ jsxs(
38156
+ const commitTitle = () => {
38157
+ if (!titleCommitEvent) return;
38158
+ const next = (titleDraft ?? "").trim();
38159
+ setTitleDraft(null);
38160
+ if (!next || next === title) return;
38161
+ eventBus.emit(`UI:${titleCommitEvent}`, { title: next, id: recordId });
38162
+ };
38163
+ const untitled = t("documentPanel.untitled") || "Untitled";
38164
+ const titleNode = titleCommitEvent && titleDraft !== null ? /* @__PURE__ */ jsx(
38165
+ Input,
38166
+ {
38167
+ value: titleDraft,
38168
+ autoFocus: true,
38169
+ "aria-label": t("common.title"),
38170
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
38171
+ onFocus: (e) => e.currentTarget.select(),
38172
+ onChange: (e) => setTitleDraft(e.target.value),
38173
+ onBlur: commitTitle,
38174
+ onKeyDown: (e) => {
38175
+ if (e.key === "Enter") {
38176
+ e.preventDefault();
38177
+ commitTitle();
38178
+ } else if (e.key === "Escape") {
38179
+ e.preventDefault();
38180
+ setTitleDraft(null);
38181
+ }
38182
+ },
38183
+ "data-testid": "document-title-input"
38184
+ }
38185
+ ) : titleCommitEvent ? /* @__PURE__ */ jsx(
38198
38186
  Box,
38199
38187
  {
38200
- className: cn(
38201
- "group relative flex items-start gap-2 rounded-sm",
38202
- "px-2 py-1 hover:bg-muted/30"
38203
- ),
38204
- "data-block-id": block.id,
38205
- "data-block-type": block.type,
38206
- children: [
38207
- !readOnly && showAffordances && /* @__PURE__ */ jsx(Box, { className: "flex w-8 shrink-0 items-center pt-1", children: /* @__PURE__ */ jsx(
38208
- BlockMenu,
38188
+ role: "button",
38189
+ tabIndex: 0,
38190
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
38191
+ onClick: () => setTitleDraft(title ?? ""),
38192
+ onKeyDown: (e) => {
38193
+ if (e.key === "Enter" || e.key === " ") {
38194
+ e.preventDefault();
38195
+ setTitleDraft(title ?? "");
38196
+ }
38197
+ },
38198
+ "data-testid": "document-title-editable",
38199
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", className: cn(!title && "text-muted-foreground"), children: title || untitled })
38200
+ }
38201
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || untitled });
38202
+ const emitWithId = (event) => () => {
38203
+ if (event) eventBus.emit(`UI:${event}`, { id: recordId });
38204
+ };
38205
+ return /* @__PURE__ */ jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
38206
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
38207
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
38208
+ titleNode,
38209
+ subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
38210
+ ] }),
38211
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
38212
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
38213
+ /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
38214
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
38215
+ editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
38216
+ /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
38217
+ editLabel ?? (t("documentPanel.edit") || "Edit")
38218
+ ] }),
38219
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
38220
+ Button,
38209
38221
  {
38210
- block,
38211
- readOnly,
38212
- onDelete,
38213
- onDuplicate,
38214
- onChangeType
38222
+ variant: "primary",
38223
+ size: "sm",
38224
+ onClick: () => fireAction(action),
38225
+ "data-testid": `action-${action.event}`,
38226
+ children: [
38227
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
38228
+ action.label
38229
+ ]
38230
+ },
38231
+ idx
38232
+ )),
38233
+ menuActions.length > 0 && /* @__PURE__ */ jsx(
38234
+ Menu,
38235
+ {
38236
+ position: "bottom-end",
38237
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
38238
+ items: menuActions.map((action) => ({
38239
+ label: action.label,
38240
+ icon: action.icon,
38241
+ variant: action.variant === "danger" ? "danger" : "default",
38242
+ onClick: () => fireAction(action)
38243
+ }))
38215
38244
  }
38216
- ) }),
38217
- /* @__PURE__ */ jsx(Box, { className: "min-w-0 flex-1", children: renderBody() })
38218
- ]
38219
- }
38220
- );
38245
+ )
38246
+ ] }) })
38247
+ ] }),
38248
+ editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
38249
+ Box,
38250
+ {
38251
+ onClick: emitWithId(editEvent),
38252
+ className: cn(
38253
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
38254
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
38255
+ ),
38256
+ "data-testid": "document-body",
38257
+ children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
38258
+ }
38259
+ )
38260
+ ] }) });
38221
38261
  }
38222
- var TOOLBAR_ENTRIES, BLOCK_TYPE_LABEL_KEY, CHANGEABLE_TYPES, _idSeq, BLOCK_TYPES, RichBlockEditor;
38223
- var init_RichBlockEditor = __esm({
38224
- "components/core/molecules/RichBlockEditor.tsx"() {
38262
+ var init_DocumentPanel = __esm({
38263
+ "components/core/molecules/DocumentPanel.tsx"() {
38225
38264
  "use client";
38226
38265
  init_cn();
38227
- init_Card();
38266
+ init_useEventBus();
38267
+ init_Box();
38268
+ init_Stack();
38228
38269
  init_Typography();
38229
38270
  init_Button();
38230
- init_Box();
38231
- init_Divider();
38232
- init_Input();
38271
+ init_Card();
38233
38272
  init_Icon();
38234
- init_useEventBus();
38235
- TOOLBAR_ENTRIES = [
38236
- { type: "paragraph", labelKey: "richBlockEditor.toolbar.text", icon: Type },
38237
- { type: "heading-1", labelKey: "richBlockEditor.toolbar.h1", icon: Heading1 },
38238
- { type: "heading-2", labelKey: "richBlockEditor.toolbar.h2", icon: Heading2 },
38239
- { type: "heading-3", labelKey: "richBlockEditor.toolbar.h3", icon: Heading3 },
38240
- { type: "bullet-list", labelKey: "richBlockEditor.toolbar.bulletList", icon: List },
38241
- { type: "numbered-list", labelKey: "richBlockEditor.toolbar.numbered", icon: ListOrdered },
38242
- { type: "quote", labelKey: "richBlockEditor.toolbar.quote", icon: Quote },
38243
- { type: "code", labelKey: "richBlockEditor.toolbar.code", icon: Code },
38244
- { type: "divider", labelKey: "richBlockEditor.toolbar.divider", icon: Minus },
38245
- { type: "image", labelKey: "richBlockEditor.toolbar.image", icon: Image$1 }
38246
- ];
38247
- BLOCK_TYPE_LABEL_KEY = {
38248
- paragraph: "richBlockEditor.blockType.paragraph",
38249
- "heading-1": "richBlockEditor.blockType.heading1",
38250
- "heading-2": "richBlockEditor.blockType.heading2",
38251
- "heading-3": "richBlockEditor.blockType.heading3",
38252
- "bullet-list": "richBlockEditor.blockType.bulletList",
38253
- "numbered-list": "richBlockEditor.blockType.numberedList",
38254
- quote: "richBlockEditor.blockType.quote",
38255
- code: "richBlockEditor.blockType.code",
38256
- divider: "richBlockEditor.blockType.divider",
38257
- image: "richBlockEditor.blockType.image"
38258
- };
38259
- CHANGEABLE_TYPES = [
38260
- "paragraph",
38261
- "heading-1",
38262
- "heading-2",
38263
- "heading-3",
38264
- "bullet-list",
38265
- "numbered-list",
38266
- "quote",
38267
- "code"
38268
- ];
38269
- _idSeq = 0;
38270
- BLOCK_TYPES = /* @__PURE__ */ new Set([
38271
- "paragraph",
38272
- "heading-1",
38273
- "heading-2",
38274
- "heading-3",
38275
- "bullet-list",
38276
- "numbered-list",
38277
- "quote",
38278
- "code",
38279
- "divider",
38280
- "image"
38281
- ]);
38282
- RichBlockEditor = ({
38283
- initialBlocks,
38284
- onChange,
38285
- changeEvent,
38286
- readOnly = false,
38287
- placeholder,
38288
- enableBlocks = true,
38289
- showToolbar = true,
38290
- className
38291
- }) => {
38292
- const { t } = useTranslate();
38293
- const [blocks, setBlocks] = useState(
38294
- () => normalizeBlocks(initialBlocks)
38295
- );
38296
- const onChangeRef = useRef(onChange);
38297
- useEffect(() => {
38298
- onChangeRef.current = onChange;
38299
- }, [onChange]);
38300
- const eventBus = useEventBus();
38301
- const changeEventRef = useRef(changeEvent);
38302
- useEffect(() => {
38303
- changeEventRef.current = changeEvent;
38304
- }, [changeEvent]);
38305
- const commit = useCallback((next) => {
38306
- setBlocks(next);
38307
- onChangeRef.current?.(next);
38308
- const evt = changeEventRef.current;
38309
- if (evt) eventBus.emit(`UI:${evt}`, { blocks: next });
38310
- }, [eventBus]);
38311
- const handleAppend = useCallback(
38312
- (type) => {
38313
- if (readOnly) return;
38314
- commit([...blocks, createBlock(type)]);
38315
- },
38316
- [blocks, commit, readOnly]
38317
- );
38318
- const handleUpdate = useCallback(
38319
- (id, updater) => {
38320
- commit(replaceBlock(blocks, id, updater));
38321
- },
38322
- [blocks, commit]
38323
- );
38324
- const handleDelete = useCallback(
38325
- (id) => {
38326
- const next = removeBlock(blocks, id);
38327
- commit(next.length > 0 ? next : [createBlock("paragraph")]);
38328
- },
38329
- [blocks, commit]
38330
- );
38331
- const handleDuplicate = useCallback(
38332
- (id) => {
38333
- const target = blocks.find((b) => b.id === id);
38334
- if (!target) return;
38335
- commit(insertAfter(blocks, id, duplicateBlock(target)));
38336
- },
38337
- [blocks, commit]
38273
+ init_Input();
38274
+ init_Menu();
38275
+ init_RichTextEditor();
38276
+ }
38277
+ });
38278
+ function renderIconInput5(icon, props) {
38279
+ return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
38280
+ }
38281
+ function fieldName(field) {
38282
+ return field.name ?? field.key ?? "";
38283
+ }
38284
+ function relationLabel(value) {
38285
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
38286
+ for (const key of ["name", "title", "label"]) {
38287
+ const candidate = value[key];
38288
+ if (typeof candidate === "string" && candidate !== "") return candidate;
38289
+ }
38290
+ const id = value.id;
38291
+ return id !== void 0 && id !== null ? String(id) : null;
38292
+ }
38293
+ function fieldKind(field, value) {
38294
+ if (field.kind) return field.kind;
38295
+ if (field.options && field.options.length > 0) return "select";
38296
+ if (typeof value === "boolean" || field.format === "boolean") return "boolean";
38297
+ if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
38298
+ return "text";
38299
+ }
38300
+ function DocumentDetails({
38301
+ entity,
38302
+ fields,
38303
+ metaCommitEvent,
38304
+ title,
38305
+ className
38306
+ }) {
38307
+ const eventBus = useEventBus();
38308
+ const { t } = useTranslate();
38309
+ const [fieldDraft, setFieldDraft] = useState(null);
38310
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
38311
+ const fieldDefs = (fields ?? []).filter((f3) => fieldName(f3) !== "");
38312
+ if (fieldDefs.length === 0) return null;
38313
+ const commitField = (name, next, current) => {
38314
+ setFieldDraft(null);
38315
+ if (!metaCommitEvent || next === current) return;
38316
+ eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
38317
+ };
38318
+ const renderValue = (field) => {
38319
+ const name = fieldName(field);
38320
+ const raw = getNestedValue(entity ?? {}, name);
38321
+ const kind = fieldKind(field, raw);
38322
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
38323
+ if (kind === "boolean") {
38324
+ return /* @__PURE__ */ jsx(
38325
+ Switch,
38326
+ {
38327
+ checked: Boolean(raw),
38328
+ disabled: !metaCommitEvent,
38329
+ "aria-label": label,
38330
+ onChange: (checked) => commitField(name, checked, Boolean(raw))
38331
+ }
38338
38332
  );
38339
- const handleChangeType = useCallback(
38340
- (id, type) => {
38341
- commit(
38342
- replaceBlock(blocks, id, (b) => changeBlockType(b, type))
38343
- );
38344
- },
38345
- [blocks, commit]
38333
+ }
38334
+ if (kind === "select") {
38335
+ return /* @__PURE__ */ jsx(
38336
+ Select,
38337
+ {
38338
+ value: raw !== void 0 && raw !== null ? String(raw) : "",
38339
+ disabled: !metaCommitEvent,
38340
+ "aria-label": label,
38341
+ className: "h-8 w-full",
38342
+ options: (field.options ?? []).map((opt) => ({ value: opt, label: humanizeEnumValue(opt) })),
38343
+ onChange: (e) => commitField(name, e.target.value, raw)
38344
+ }
38346
38345
  );
38347
- return /* @__PURE__ */ jsxs(
38348
- Card,
38346
+ }
38347
+ if (kind === "readonly" || !metaCommitEvent) {
38348
+ if (Array.isArray(raw)) {
38349
+ if (raw.length === 0) {
38350
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
38351
+ }
38352
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
38353
+ }
38354
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
38355
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
38356
+ }
38357
+ if (fieldDraft?.name === name) {
38358
+ return /* @__PURE__ */ jsx(
38359
+ Input,
38349
38360
  {
38350
- variant: "bordered",
38351
- padding: "none",
38352
- className: cn("flex flex-col text-card-foreground", className),
38353
- children: [
38354
- enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsx(
38355
- Box,
38356
- {
38357
- role: "toolbar",
38358
- "aria-label": t("richBlockEditor.editorToolbar"),
38359
- className: cn(
38360
- "flex flex-wrap items-center gap-1",
38361
- "border-b border-border bg-muted/30 px-2 py-2"
38362
- ),
38363
- children: TOOLBAR_ENTRIES.map((entry) => {
38364
- const Icon2 = entry.icon;
38365
- const entryLabel = t(entry.labelKey);
38366
- return /* @__PURE__ */ jsxs(
38367
- Button,
38368
- {
38369
- type: "button",
38370
- variant: "ghost",
38371
- size: "sm",
38372
- "aria-label": t("richBlockEditor.insertEntry", { label: entryLabel }),
38373
- title: entryLabel,
38374
- onClick: () => handleAppend(entry.type),
38375
- children: [
38376
- /* @__PURE__ */ jsx(Icon2, { size: 14 }),
38377
- /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "ml-1 hidden text-xs sm:inline", children: entryLabel })
38378
- ]
38379
- },
38380
- entry.type
38381
- );
38382
- })
38383
- }
38384
- ),
38385
- /* @__PURE__ */ jsx(Box, { className: "flex flex-col gap-1 px-3 py-3", children: blocks.map((block) => /* @__PURE__ */ jsx(
38386
- BlockRow,
38387
- {
38388
- block,
38389
- readOnly,
38390
- showAffordances: enableBlocks,
38391
- placeholder,
38392
- onUpdate: (updater) => handleUpdate(block.id, updater),
38393
- onDelete: () => handleDelete(block.id),
38394
- onDuplicate: () => handleDuplicate(block.id),
38395
- onChangeType: (type) => handleChangeType(block.id, type)
38396
- },
38397
- block.id
38398
- )) })
38399
- ]
38361
+ value: fieldDraft.value,
38362
+ autoFocus: true,
38363
+ "aria-label": label,
38364
+ className: "h-8 w-full",
38365
+ onChange: (e) => setFieldDraft({ name, value: e.target.value }),
38366
+ onBlur: () => commitField(name, fieldDraft.value.trim(), raw),
38367
+ onKeyDown: (e) => {
38368
+ if (e.key === "Enter") {
38369
+ e.preventDefault();
38370
+ commitField(name, fieldDraft.value.trim(), raw);
38371
+ } else if (e.key === "Escape") {
38372
+ e.preventDefault();
38373
+ setFieldDraft(null);
38374
+ }
38375
+ },
38376
+ "data-testid": `document-property-input-${name}`
38400
38377
  }
38401
38378
  );
38402
- };
38403
- RichBlockEditor.displayName = "RichBlockEditor";
38379
+ }
38380
+ const shown = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
38381
+ return /* @__PURE__ */ jsx(
38382
+ Box,
38383
+ {
38384
+ role: "button",
38385
+ tabIndex: 0,
38386
+ className: cn(
38387
+ "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
38388
+ (raw === void 0 || raw === null || raw === "") && "text-muted-foreground"
38389
+ ),
38390
+ onClick: () => setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" }),
38391
+ onKeyDown: (e) => {
38392
+ if (e.key === "Enter" || e.key === " ") {
38393
+ e.preventDefault();
38394
+ setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" });
38395
+ }
38396
+ },
38397
+ "data-testid": `document-property-${name}`,
38398
+ children: /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown })
38399
+ }
38400
+ );
38401
+ };
38402
+ return /* @__PURE__ */ jsx(Card, { variant: "bordered", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-4", children: [
38403
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", weight: "medium", className: "uppercase tracking-wide", children: title ?? (t("documentDetails.title") || "Details") }),
38404
+ /* @__PURE__ */ jsx(VStack, { gap: "sm", children: fieldDefs.map((field) => {
38405
+ const name = fieldName(field);
38406
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
38407
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
38408
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
38409
+ field.icon && renderIconInput5(field.icon, { size: "xs", className: "text-muted-foreground" }),
38410
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: label })
38411
+ ] }),
38412
+ /* @__PURE__ */ jsx(Box, { className: "min-w-0", children: renderValue(field) })
38413
+ ] }, name);
38414
+ }) })
38415
+ ] }) });
38416
+ }
38417
+ var init_DocumentDetails = __esm({
38418
+ "components/core/molecules/DocumentDetails.tsx"() {
38419
+ "use client";
38420
+ init_cn();
38421
+ init_format();
38422
+ init_getNestedValue();
38423
+ init_useEventBus();
38424
+ init_Box();
38425
+ init_Stack();
38426
+ init_Typography();
38427
+ init_Badge();
38428
+ init_Card();
38429
+ init_Icon();
38430
+ init_Input();
38431
+ init_Select();
38432
+ init_Switch();
38404
38433
  }
38405
38434
  });
38406
38435
  function collectInitiallyCollapsed(nodes, acc) {
@@ -43074,7 +43103,9 @@ var init_molecules2 = __esm({
43074
43103
  init_QrScanner();
43075
43104
  init_OptionConstraintGroup();
43076
43105
  init_PositionedCanvas();
43077
- init_RichBlockEditor();
43106
+ init_RichTextEditor();
43107
+ init_DocumentPanel();
43108
+ init_DocumentDetails();
43078
43109
  init_ReplyTree();
43079
43110
  init_BranchingLogicBuilder();
43080
43111
  init_VersionDiff();
@@ -43574,8 +43605,8 @@ var init_DataTable = __esm({
43574
43605
  DataTable.displayName = "DataTable";
43575
43606
  }
43576
43607
  });
43577
- function getBadgeVariant(fieldName, value) {
43578
- const name = fieldName.toLowerCase();
43608
+ function getBadgeVariant(fieldName2, value) {
43609
+ const name = fieldName2.toLowerCase();
43579
43610
  const val = String(value).toLowerCase();
43580
43611
  if (name.includes("status")) {
43581
43612
  if (val.includes("complete") || val.includes("done") || val.includes("active"))
@@ -43591,12 +43622,12 @@ function getBadgeVariant(fieldName, value) {
43591
43622
  }
43592
43623
  return "default";
43593
43624
  }
43594
- function formatFieldValue2(value, fieldName) {
43625
+ function formatFieldValue2(value, fieldName2) {
43595
43626
  if (typeof value === "number") {
43596
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
43627
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
43597
43628
  return `${value}%`;
43598
43629
  }
43599
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
43630
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
43600
43631
  return `$${value.toLocaleString()}`;
43601
43632
  }
43602
43633
  return value.toLocaleString();
@@ -43606,7 +43637,7 @@ function formatFieldValue2(value, fieldName) {
43606
43637
  }
43607
43638
  return String(value);
43608
43639
  }
43609
- function renderRichFieldValue(value, fieldName, fieldType, meta) {
43640
+ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43610
43641
  if (value === void 0 || value === null) return "\u2014";
43611
43642
  const str2 = String(value);
43612
43643
  switch (fieldType) {
@@ -43617,7 +43648,7 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
43617
43648
  "img",
43618
43649
  {
43619
43650
  src: str2,
43620
- alt: formatFieldLabel(fieldName),
43651
+ alt: formatFieldLabel(fieldName2),
43621
43652
  className: "max-w-full max-h-64 rounded-md object-contain",
43622
43653
  loading: "lazy"
43623
43654
  }
@@ -43752,9 +43783,9 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
43752
43783
  return /* @__PURE__ */ jsx("a", { href: `tel:${str2}`, className: "text-primary hover:underline", children: str2 });
43753
43784
  default:
43754
43785
  if (meta?.values && meta.values.length > 0 && meta.values.includes(str2)) {
43755
- return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName, str2), children: humanizeEnumValue(str2) });
43786
+ return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName2, str2), children: humanizeEnumValue(str2) });
43756
43787
  }
43757
- return formatFieldValue2(value, fieldName);
43788
+ return formatFieldValue2(value, fieldName2);
43758
43789
  }
43759
43790
  }
43760
43791
  function normalizeFieldDefs(fields) {
@@ -44224,16 +44255,11 @@ var init_DetailPanel = __esm({
44224
44255
  footer
44225
44256
  ] })
44226
44257
  ] }) });
44227
- return /* @__PURE__ */ jsx(
44228
- Box,
44229
- {
44230
- className: cn(
44231
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
44232
- className
44233
- ),
44234
- children: content
44235
- }
44236
- );
44258
+ if (!slideOver) {
44259
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
44260
+ }
44261
+ const panel = /* @__PURE__ */ jsx(Box, { className: cn("fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6", className), children: content });
44262
+ return typeof document === "undefined" ? panel : createPortal(panel, document.body);
44237
44263
  };
44238
44264
  DetailPanel.displayName = "DetailPanel";
44239
44265
  }
@@ -44602,7 +44628,21 @@ var init_Form = __esm({
44602
44628
  const normalizedInitialData = React77__default.useMemo(() => {
44603
44629
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
44604
44630
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
44605
- return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
44631
+ const merged = entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
44632
+ const toId = (value) => {
44633
+ if (value === null || value === void 0 || typeof value !== "object" || value instanceof Date) return value;
44634
+ if (Array.isArray(value)) {
44635
+ return value.map(
44636
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && item.id !== void 0 && item.id !== null ? String(item.id) : item
44637
+ );
44638
+ }
44639
+ return value.id !== void 0 && value.id !== null ? String(value.id) : value;
44640
+ };
44641
+ const normalized = {};
44642
+ for (const [key, value] of Object.entries(merged)) {
44643
+ normalized[key] = key === "id" ? value : toId(value);
44644
+ }
44645
+ return normalized;
44606
44646
  }, [entity, initialData]);
44607
44647
  const entityDerivedFields = React77__default.useMemo(() => {
44608
44648
  if (fields && fields.length > 0) return void 0;
@@ -44731,8 +44771,8 @@ var init_Form = __esm({
44731
44771
  checkViolations(name, newFormData);
44732
44772
  };
44733
44773
  const isFieldVisible = React77__default.useCallback(
44734
- (fieldName) => {
44735
- const condition = conditionalFields[fieldName];
44774
+ (fieldName2) => {
44775
+ const condition = conditionalFields[fieldName2];
44736
44776
  if (!condition) return true;
44737
44777
  return Boolean(evaluateFormExpression(condition, evalContext));
44738
44778
  },
@@ -44778,9 +44818,9 @@ var init_Form = __esm({
44778
44818
  };
44779
44819
  const handleInvalid = (e) => {
44780
44820
  const target = e.target;
44781
- const fieldName = target.getAttribute("data-field-name") ?? target.name ?? "";
44821
+ const fieldName2 = target.getAttribute("data-field-name") ?? target.name ?? "";
44782
44822
  const fieldMessage = target.validationMessage || "Invalid value";
44783
- debug("forms", "invalid", { mode: formMode, fieldName, fieldMessage });
44823
+ debug("forms", "invalid", { mode: formMode, fieldName: fieldName2, fieldMessage });
44784
44824
  queueMicrotask(() => {
44785
44825
  const form = formRef.current;
44786
44826
  if (!form) return;
@@ -44816,22 +44856,22 @@ var init_Form = __esm({
44816
44856
  };
44817
44857
  const renderField = React77__default.useCallback(
44818
44858
  (field) => {
44819
- const fieldName = field.name || field.field;
44820
- if (!fieldName) return null;
44821
- if (!isFieldVisible(fieldName)) {
44859
+ const fieldName2 = field.name || field.field;
44860
+ if (!fieldName2) return null;
44861
+ if (!isFieldVisible(fieldName2)) {
44822
44862
  return null;
44823
44863
  }
44824
44864
  const inputType = determineInputType(field);
44825
- const label = field.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, " $1");
44826
- const currentValue2 = formData[fieldName] ?? field.defaultValue ?? "";
44827
- return /* @__PURE__ */ jsxs(VStack, { gap: "xs", "data-field": fieldName, children: [
44865
+ const label = field.label || fieldName2.charAt(0).toUpperCase() + fieldName2.slice(1).replace(/([A-Z])/g, " $1");
44866
+ const currentValue2 = formData[fieldName2] ?? field.defaultValue ?? "";
44867
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", "data-field": fieldName2, children: [
44828
44868
  inputType !== "checkbox" && /* @__PURE__ */ jsxs(Typography, { as: "label", variant: "label", weight: "bold", children: [
44829
44869
  label,
44830
44870
  field.required && /* @__PURE__ */ jsx(Typography, { as: "span", color: "error", className: "ml-1", children: "*" })
44831
44871
  ] }),
44832
- renderFieldInput(field, fieldName, inputType, currentValue2, label),
44872
+ renderFieldInput(field, fieldName2, inputType, currentValue2, label),
44833
44873
  field.hint && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: field.hint })
44834
- ] }, fieldName);
44874
+ ] }, fieldName2);
44835
44875
  },
44836
44876
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
44837
44877
  );
@@ -44860,8 +44900,8 @@ var init_Form = __esm({
44860
44900
  }
44861
44901
  return field;
44862
44902
  }).map((field) => {
44863
- const fieldName = field.name || field.field;
44864
- const override = fieldOverrides?.find((o) => o.name === fieldName);
44903
+ const fieldName2 = field.name || field.field;
44904
+ const override = fieldOverrides?.find((o) => o.name === fieldName2);
44865
44905
  if (!override) return field;
44866
44906
  return {
44867
44907
  ...field,
@@ -44917,11 +44957,11 @@ var init_Form = __esm({
44917
44957
  ] }, section.id);
44918
44958
  }).filter(Boolean);
44919
44959
  }, [sections, isSectionVisible, collapsedSections, renderField, gap]);
44920
- function renderFieldInput(field, fieldName, inputType, currentValue2, label) {
44960
+ function renderFieldInput(field, fieldName2, inputType, currentValue2, label) {
44921
44961
  const commonProps = {
44922
- id: fieldName,
44923
- name: fieldName,
44924
- "data-field-name": fieldName,
44962
+ id: fieldName2,
44963
+ name: fieldName2,
44964
+ "data-field-name": fieldName2,
44925
44965
  required: field.required,
44926
44966
  disabled: isLoading,
44927
44967
  placeholder: field.placeholder,
@@ -44935,7 +44975,7 @@ var init_Form = __esm({
44935
44975
  ...commonProps,
44936
44976
  label: label + (field.required ? " *" : ""),
44937
44977
  checked: Boolean(currentValue2),
44938
- onChange: (e) => handleChange(fieldName, e.target.checked)
44978
+ onChange: (e) => handleChange(fieldName2, e.target.checked)
44939
44979
  }
44940
44980
  );
44941
44981
  case "textarea":
@@ -44944,7 +44984,7 @@ var init_Form = __esm({
44944
44984
  {
44945
44985
  ...commonProps,
44946
44986
  value: String(currentValue2),
44947
- onChange: (e) => handleChange(fieldName, e.target.value),
44987
+ onChange: (e) => handleChange(fieldName2, e.target.value),
44948
44988
  minLength: field.min,
44949
44989
  maxLength: field.max
44950
44990
  }
@@ -44957,14 +44997,14 @@ var init_Form = __esm({
44957
44997
  ...commonProps,
44958
44998
  options,
44959
44999
  value: String(currentValue2),
44960
- onValueChange: (v) => handleChange(fieldName, v),
45000
+ onValueChange: (v) => handleChange(fieldName2, v),
44961
45001
  placeholder: field.placeholder || `Select ${label}...`
44962
45002
  }
44963
45003
  );
44964
45004
  }
44965
45005
  case "relation": {
44966
- const relationOptions = relationsData[fieldName] || [];
44967
- const relationLoading = relationsLoading[fieldName] || false;
45006
+ const relationOptions = relationsData[fieldName2] || [];
45007
+ const relationLoading = relationsLoading[fieldName2] || false;
44968
45008
  if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
44969
45009
  const selectedValues = Array.isArray(currentValue2) ? currentValue2.map((v) => String(v)) : [];
44970
45010
  return /* @__PURE__ */ jsx(
@@ -44976,7 +45016,7 @@ var init_Form = __esm({
44976
45016
  clearable: true,
44977
45017
  options: [...relationOptions],
44978
45018
  value: selectedValues,
44979
- onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
45019
+ onValueChange: (value) => handleChange(fieldName2, Array.isArray(value) ? value : [value]),
44980
45020
  placeholder: field.placeholder || `Select ${label}...`
44981
45021
  }
44982
45022
  );
@@ -44986,7 +45026,7 @@ var init_Form = __esm({
44986
45026
  {
44987
45027
  ...commonProps,
44988
45028
  value: currentValue2 ? String(currentValue2) : void 0,
44989
- onChange: (value) => handleChange(fieldName, value),
45029
+ onChange: (value) => handleChange(fieldName2, value),
44990
45030
  options: relationOptions,
44991
45031
  isLoading: relationLoading,
44992
45032
  placeholder: field.placeholder || `Select ${label}...`,
@@ -45003,7 +45043,7 @@ var init_Form = __esm({
45003
45043
  placeholder: field.placeholder,
45004
45044
  disabled: isLoading,
45005
45045
  value: arrayValue,
45006
- onChange: (next) => handleChange(fieldName, [...next])
45046
+ onChange: (next) => handleChange(fieldName2, [...next])
45007
45047
  }
45008
45048
  );
45009
45049
  }
@@ -45015,7 +45055,7 @@ var init_Form = __esm({
45015
45055
  type: "number",
45016
45056
  value: currentValue2 !== void 0 && currentValue2 !== "" ? String(currentValue2) : "",
45017
45057
  onChange: (e) => handleChange(
45018
- fieldName,
45058
+ fieldName2,
45019
45059
  e.target.value ? Number(e.target.value) : void 0
45020
45060
  ),
45021
45061
  min: field.min,
@@ -45032,7 +45072,7 @@ var init_Form = __esm({
45032
45072
  icon: DollarSign,
45033
45073
  value: currentValue2 !== void 0 && currentValue2 !== "" ? String(currentValue2) : "",
45034
45074
  onChange: (e) => handleChange(
45035
- fieldName,
45075
+ fieldName2,
45036
45076
  e.target.value ? Number(e.target.value) : void 0
45037
45077
  ),
45038
45078
  min: field.min,
@@ -45050,7 +45090,7 @@ var init_Form = __esm({
45050
45090
  const f3 = files[0];
45051
45091
  if (!f3) return;
45052
45092
  const reader = new FileReader();
45053
- reader.onload = () => handleChange(fieldName, {
45093
+ reader.onload = () => handleChange(fieldName2, {
45054
45094
  name: f3.name,
45055
45095
  mimeType: f3.type,
45056
45096
  sizeBytes: f3.size,
@@ -45067,7 +45107,7 @@ var init_Form = __esm({
45067
45107
  ...commonProps,
45068
45108
  type: "date",
45069
45109
  value: formatDateValue(currentValue2),
45070
- onChange: (e) => handleChange(fieldName, e.target.value)
45110
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45071
45111
  }
45072
45112
  );
45073
45113
  case "datetime-local":
@@ -45077,7 +45117,7 @@ var init_Form = __esm({
45077
45117
  ...commonProps,
45078
45118
  type: "datetime-local",
45079
45119
  value: formatDateTimeValue(currentValue2),
45080
- onChange: (e) => handleChange(fieldName, e.target.value)
45120
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45081
45121
  }
45082
45122
  );
45083
45123
  case "email":
@@ -45087,7 +45127,7 @@ var init_Form = __esm({
45087
45127
  ...commonProps,
45088
45128
  type: "email",
45089
45129
  value: String(currentValue2),
45090
- onChange: (e) => handleChange(fieldName, e.target.value),
45130
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45091
45131
  minLength: field.min,
45092
45132
  maxLength: field.max
45093
45133
  }
@@ -45099,7 +45139,7 @@ var init_Form = __esm({
45099
45139
  ...commonProps,
45100
45140
  type: "url",
45101
45141
  value: String(currentValue2),
45102
- onChange: (e) => handleChange(fieldName, e.target.value),
45142
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45103
45143
  minLength: field.min,
45104
45144
  maxLength: field.max
45105
45145
  }
@@ -45111,7 +45151,7 @@ var init_Form = __esm({
45111
45151
  ...commonProps,
45112
45152
  type: "password",
45113
45153
  value: String(currentValue2),
45114
- onChange: (e) => handleChange(fieldName, e.target.value),
45154
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45115
45155
  minLength: field.min,
45116
45156
  maxLength: field.max
45117
45157
  }
@@ -45124,7 +45164,7 @@ var init_Form = __esm({
45124
45164
  ...commonProps,
45125
45165
  type: "text",
45126
45166
  value: String(currentValue2),
45127
- onChange: (e) => handleChange(fieldName, e.target.value),
45167
+ onChange: (e) => handleChange(fieldName2, e.target.value),
45128
45168
  minLength: field.min,
45129
45169
  maxLength: field.max
45130
45170
  }
@@ -45481,7 +45521,7 @@ function entityFieldsFromListItem(item) {
45481
45521
  }
45482
45522
  return result;
45483
45523
  }
45484
- function getStatusStyle(fieldName, value) {
45524
+ function getStatusStyle(fieldName2, value) {
45485
45525
  const val = String(value).toLowerCase();
45486
45526
  if (val.includes("complete") || val.includes("done"))
45487
45527
  return STATUS_STYLES.complete;
@@ -45497,12 +45537,12 @@ function getStatusStyle(fieldName, value) {
45497
45537
  if (val.includes("low")) return STATUS_STYLES.low;
45498
45538
  return STATUS_STYLES.default;
45499
45539
  }
45500
- function formatValue3(value, fieldName) {
45540
+ function formatValue3(value, fieldName2) {
45501
45541
  if (typeof value === "number") {
45502
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
45542
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
45503
45543
  return `${value}%`;
45504
45544
  }
45505
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
45545
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
45506
45546
  return new Intl.NumberFormat("en-US", {
45507
45547
  style: "currency",
45508
45548
  currency: "USD",
@@ -45525,8 +45565,8 @@ function formatValue3(value, fieldName) {
45525
45565
  }
45526
45566
  return String(value);
45527
45567
  }
45528
- function formatFieldLabel2(fieldName) {
45529
- return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
45568
+ function formatFieldLabel2(fieldName2) {
45569
+ return humanizeFieldName(fieldName2).replace(/\sId$/, "").trim();
45530
45570
  }
45531
45571
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
45532
45572
  var init_List = __esm({
@@ -45594,9 +45634,9 @@ var init_List = __esm({
45594
45634
  };
45595
45635
  StatusBadge = ({
45596
45636
  value,
45597
- fieldName
45637
+ fieldName: fieldName2
45598
45638
  }) => {
45599
- const style = getStatusStyle(fieldName, value);
45639
+ const style = getStatusStyle(fieldName2, value);
45600
45640
  return /* @__PURE__ */ jsxs(
45601
45641
  Typography,
45602
45642
  {
@@ -48509,8 +48549,8 @@ var init_StatCard = __esm({
48509
48549
  return items.length;
48510
48550
  }
48511
48551
  if (field.includes(":")) {
48512
- const [fieldName, fieldValue] = field.split(":");
48513
- return items.filter((item) => item[fieldName] === fieldValue).length;
48552
+ const [fieldName2, fieldValue] = field.split(":");
48553
+ return items.filter((item) => item[fieldName2] === fieldValue).length;
48514
48554
  }
48515
48555
  const fieldExistsOnItems = items.some((item) => field in item);
48516
48556
  if (fieldExistsOnItems) {
@@ -50030,6 +50070,8 @@ var init_component_registry_generated = __esm({
50030
50070
  init_DocSearch();
50031
50071
  init_DocSidebar();
50032
50072
  init_DocTOC();
50073
+ init_DocumentDetails();
50074
+ init_DocumentPanel();
50033
50075
  init_DocumentViewer();
50034
50076
  init_DrawFxLayer();
50035
50077
  init_DrawGroup();
@@ -50139,7 +50181,7 @@ var init_component_registry_generated = __esm({
50139
50181
  init_RelationSelect();
50140
50182
  init_RepeatableFormSection();
50141
50183
  init_ReplyTree();
50142
- init_RichBlockEditor();
50184
+ init_RichTextEditor();
50143
50185
  init_RuntimeDebugger2();
50144
50186
  init_ScaledDiagram();
50145
50187
  init_ScoreDisplay();
@@ -50302,6 +50344,8 @@ var init_component_registry_generated = __esm({
50302
50344
  "DocSearch": DocSearch,
50303
50345
  "DocSidebar": DocSidebar,
50304
50346
  "DocTOC": DocTOC,
50347
+ "DocumentDetails": DocumentDetails,
50348
+ "DocumentPanel": DocumentPanel,
50305
50349
  "DocumentViewer": DocumentViewer,
50306
50350
  "DrawFxLayer": DrawFxLayer,
50307
50351
  "DrawGroup": DrawGroup,
@@ -50413,7 +50457,7 @@ var init_component_registry_generated = __esm({
50413
50457
  "RelationSelect": RelationSelect,
50414
50458
  "RepeatableFormSection": RepeatableFormSection,
50415
50459
  "ReplyTree": ReplyTree,
50416
- "RichBlockEditor": RichBlockEditor,
50460
+ "RichTextEditor": RichTextEditor,
50417
50461
  "RuntimeDebugger": RuntimeDebugger,
50418
50462
  "ScaledDiagram": ScaledDiagram,
50419
50463
  "ScoreDisplay": ScoreDisplay,
@@ -50567,10 +50611,10 @@ function enrichFormFields(fields, entityDef) {
50567
50611
  }
50568
50612
  if (field && typeof field === "object" && !Array.isArray(field) && !React77__default.isValidElement(field) && !(field instanceof Date)) {
50569
50613
  const obj = field;
50570
- const fieldName = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
50571
- if (!fieldName) return field;
50614
+ const fieldName2 = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
50615
+ if (!fieldName2) return field;
50572
50616
  if (obj.type || obj.inputType) return field;
50573
- const entityField = fieldMap.get(fieldName);
50617
+ const entityField = fieldMap.get(fieldName2);
50574
50618
  if (!entityField) return field;
50575
50619
  const enriched = { ...obj, type: entityField.type };
50576
50620
  if (entityField.required && !("required" in obj)) {
@@ -50620,9 +50664,9 @@ function enrichDetailFields(fields, entityDef) {
50620
50664
  }
50621
50665
  if (field && typeof field === "object" && !Array.isArray(field) && !React77__default.isValidElement(field) && !(field instanceof Date)) {
50622
50666
  const obj = field;
50623
- const fieldName = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
50624
- if (!fieldName || obj.type) return field;
50625
- const meta = metaFor(fieldName);
50667
+ const fieldName2 = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
50668
+ if (!fieldName2 || obj.type) return field;
50669
+ const meta = metaFor(fieldName2);
50626
50670
  return meta ? { ...obj, ...meta } : field;
50627
50671
  }
50628
50672
  return field;
@@ -51461,9 +51505,9 @@ function UISlotRenderer({
51461
51505
  "ui-slot-renderer relative min-h-full",
51462
51506
  className
51463
51507
  ), children: [
51464
- /* @__PURE__ */ jsxs(Box, { className: "flex min-h-full", children: [
51465
- /* @__PURE__ */ jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 shrink-0" }),
51466
- /* @__PURE__ */ jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-h-[200px]" })
51508
+ /* @__PURE__ */ jsxs(Box, { className: "flex min-h-full flex-col lg:flex-row", children: [
51509
+ /* @__PURE__ */ jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 lg:shrink-0" }),
51510
+ /* @__PURE__ */ jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-w-0 min-h-[200px]" })
51467
51511
  ] }),
51468
51512
  /* @__PURE__ */ jsx(UISlotComponent, { slot: "modal", portal: true }),
51469
51513
  /* @__PURE__ */ jsx(UISlotComponent, { slot: "drawer", portal: true }),
@@ -53451,54 +53495,6 @@ var en_default = {
53451
53495
  "template.faq": "Frequently Asked Questions",
53452
53496
  "template.ourTeam": "Our Team",
53453
53497
  "template.caseStudies": "Case Studies",
53454
- "richBlockEditor.toolbar.text": "Text",
53455
- "richBlockEditor.toolbar.h1": "H1",
53456
- "richBlockEditor.toolbar.h2": "H2",
53457
- "richBlockEditor.toolbar.h3": "H3",
53458
- "richBlockEditor.toolbar.bulletList": "Bullet list",
53459
- "richBlockEditor.toolbar.numbered": "Numbered",
53460
- "richBlockEditor.toolbar.quote": "Quote",
53461
- "richBlockEditor.toolbar.code": "Code",
53462
- "richBlockEditor.toolbar.divider": "Divider",
53463
- "richBlockEditor.toolbar.image": "Image",
53464
- "richBlockEditor.blockType.paragraph": "Text",
53465
- "richBlockEditor.blockType.heading1": "Heading 1",
53466
- "richBlockEditor.blockType.heading2": "Heading 2",
53467
- "richBlockEditor.blockType.heading3": "Heading 3",
53468
- "richBlockEditor.blockType.bulletList": "Bullet list",
53469
- "richBlockEditor.blockType.numberedList": "Numbered list",
53470
- "richBlockEditor.blockType.quote": "Quote",
53471
- "richBlockEditor.blockType.code": "Code",
53472
- "richBlockEditor.blockType.divider": "Divider",
53473
- "richBlockEditor.blockType.image": "Image",
53474
- "richBlockEditor.blockActions": "Block actions",
53475
- "richBlockEditor.duplicate": "Duplicate",
53476
- "richBlockEditor.turnInto": "Turn into",
53477
- "richBlockEditor.placeholder.heading1": "Heading 1",
53478
- "richBlockEditor.placeholder.heading2": "Heading 2",
53479
- "richBlockEditor.placeholder.heading3": "Heading 3",
53480
- "richBlockEditor.placeholder.quote": "Quote",
53481
- "richBlockEditor.placeholder.code": "Enter code",
53482
- "richBlockEditor.placeholder.paragraph": "Start writing...",
53483
- "richBlockEditor.placeholder.listItem": "List item",
53484
- "richBlockEditor.placeholder.caption": "Caption (optional)",
53485
- "richBlockEditor.aria.heading1Block": "Heading 1 block",
53486
- "richBlockEditor.aria.heading2Block": "Heading 2 block",
53487
- "richBlockEditor.aria.heading3Block": "Heading 3 block",
53488
- "richBlockEditor.aria.quoteBlock": "Quote block",
53489
- "richBlockEditor.aria.codeBlock": "Code block",
53490
- "richBlockEditor.aria.codeLanguage": "Code language",
53491
- "richBlockEditor.aria.imageUrl": "Image URL",
53492
- "richBlockEditor.aria.imageCaption": "Image caption",
53493
- "richBlockEditor.aria.listItem": "List item",
53494
- "richBlockEditor.aria.removeListItem": "Remove list item",
53495
- "richBlockEditor.aria.paragraphBlock": "Paragraph block",
53496
- "richBlockEditor.embeddedImage": "Embedded image",
53497
- "richBlockEditor.noImageUrl": "No image URL set",
53498
- "richBlockEditor.addItem": "Add item",
53499
- "richBlockEditor.insertParagraphBelow": "Insert paragraph below",
53500
- "richBlockEditor.editorToolbar": "Block editor toolbar",
53501
- "richBlockEditor.insertEntry": "Insert {{label}}",
53502
53498
  "versionDiff.compare": "Compare",
53503
53499
  "versionDiff.to": "to",
53504
53500
  "versionDiff.beforeRevision": "Before revision",
@@ -53846,7 +53842,31 @@ var en_default = {
53846
53842
  "td.survivedAllWaves": "Survived all waves!",
53847
53843
  "td.victory": "Victory!",
53848
53844
  "td.wave": "Wave",
53849
- "td.waveInProgress": "Wave in progress\u2026"
53845
+ "td.waveInProgress": "Wave in progress\u2026",
53846
+ "documentDetails.title": "Details",
53847
+ "documentPanel.untitled": "Untitled",
53848
+ "documentPanel.edit": "Edit",
53849
+ "documentPanel.done": "Done",
53850
+ "documentPanel.autosaveHint": "Saves as you type",
53851
+ "documentPanel.placeholder": "Start writing\u2026",
53852
+ "richTextEditor.editorToolbar": "Formatting toolbar",
53853
+ "richTextEditor.editorSurface": "Rich text editor",
53854
+ "richTextEditor.bold": "Bold",
53855
+ "richTextEditor.italic": "Italic",
53856
+ "richTextEditor.underline": "Underline",
53857
+ "richTextEditor.strikethrough": "Strikethrough",
53858
+ "richTextEditor.heading1": "Heading 1",
53859
+ "richTextEditor.heading2": "Heading 2",
53860
+ "richTextEditor.heading3": "Heading 3",
53861
+ "richTextEditor.bulletList": "Bullet list",
53862
+ "richTextEditor.numberedList": "Numbered list",
53863
+ "richTextEditor.quote": "Quote",
53864
+ "richTextEditor.code": "Code",
53865
+ "richTextEditor.link": "Add link",
53866
+ "richTextEditor.removeLink": "Remove link",
53867
+ "richTextEditor.divider": "Divider",
53868
+ "richTextEditor.linkPrompt": "Link address",
53869
+ "richTextEditor.placeholder": "Start writing\u2026"
53850
53870
  };
53851
53871
 
53852
53872
  // hooks/useTranslate.ts
@@ -53867,7 +53887,7 @@ var I18nContext = createContext({
53867
53887
  });
53868
53888
  I18nContext.displayName = "I18nContext";
53869
53889
  var I18nProvider = I18nContext.Provider;
53870
- function useTranslate114() {
53890
+ function useTranslate116() {
53871
53891
  return useContext(I18nContext);
53872
53892
  }
53873
53893
  function createTranslate(messages) {
@@ -54149,4 +54169,4 @@ function useGitHubBranches(owner, repo, enabled = true) {
54149
54169
  });
54150
54170
  }
54151
54171
 
54152
- export { ALL_PRESETS, ALMADAR_DND_MIME, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AlgoGraphCanvas, AlgorithmCanvas, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AtlasImage, AtlasPanel, AuthLayout, Avatar, Badge, BehaviorView, BiologyCanvas, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, Button, ButtonGroup, CTABanner, CalendarGrid, Canvas, Canvas2D, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, Center, Chart, ChartLegend, ChatBar, Checkbox, ChemistryCanvas, ChoiceButton, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, ControlGrid, CounterTemplate, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DEFAULT_SLOTS, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DetailPanel, Dialog, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, ELEMENT_SELECTED_EVENT, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmojiPicker, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, FxOverlay, GameAudioToggle, GameHud, GameIcon, GameMenu, GameShell, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HeroOrganism, HeroSection, I18nProvider, IDENTITY_BOOK_FIELDS, Icon, IconPicker, ImportPreviewTree, ImportProgress, ImportSourcePicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, LearningCanvas, LearningScene3D, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MathCanvas, MatrixQuestion, MediaGallery, Menu, Meter, Modal, ModalSlot, ModuleCard, Navigation, NodeSlotEditor, NotifyListener, NumberStepper, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, PageTransition, Pagination, PatternTile, PhysicsCanvas, Popover, PositionedCanvas, Presence, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, RichBlockEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, ServiceCatalog, SharedEntityStoreContext, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateGraph, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StepFlow, StepFlowOrganism, SubagentTracePanel, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UploadDropZone, VStack, VersionDiff, ViolationAlert, VoteStack, WizardContainer, WizardNavigation, WizardProgress, arrowBetween, billboardLabel, boardEntity, bool, calculateAttackTargets, calculateValidMoves, cn, createInitialGameState, createSharedEntityStore, createTranslate, createUnitAnimationState, cylinderBetween, get3DClickPayload, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, makeAsset, makeAssetMap, mapBookData, meshSphere, num, objAvailableActions, objAvailableEvents, objCurrentState, objIcon, objId, objMaxRules, objName, objRules, objStates, parseEditFocus, parseLessonSegments, parseMarkdownWithCodeBlocks, parseQueryBinding, pendulum, projectileMotion, registerCodeLanguageLoader, renderPatternValue, resolveFieldMap, resolveFrame, resolveSheetDirection, rows, runTickFrame, screenToIso, springOscillator, str, tickAnimationState, toCodeLanguage, transitionAnimation, unitHealth, unitPosition, unitTeam, useAgentChat, useAnchorRect, useAtlasSliceDataUrl, useAuthContext, useCamera, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useEmitEvent, useEventBus, useEventListener, useExtensions, useFileEditor, useFileSystem, useGameAudio, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useImageCache, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePresence, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate114 as useTranslate, useUIEvents, useUISlotManager, useUnitSpriteAtlas, useValidation, vec2 };
54172
+ export { ALL_PRESETS, ALMADAR_DND_MIME, AR_BOOK_FIELDS, AboutPageTemplate, Accordion, Card2 as ActionCard, ActionPalette, ActionTile, ActivationBlock, Alert, AlgoGraphCanvas, AlgorithmCanvas, AnimatedCounter, AnimatedGraphic, AnimatedReveal, ArticleSection, Aside, AssetPicker, AtlasImage, AtlasPanel, AuthLayout, Avatar, Badge, BehaviorView, BiologyCanvas, BloomQuizBlock, BookChapterView, BookCoverPage, BookNavBar, BookTableOfContents, BookViewer, Box, BranchingLogicBuilder, Breadcrumb, Button, ButtonGroup, CTABanner, CalendarGrid, Canvas, Canvas2D, Card, CardBody, CardContent, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, CaseStudyCard, CaseStudyOrganism, Center, Chart, ChartLegend, ChatBar, Checkbox, ChemistryCanvas, ChoiceButton, Coachmark, CodeBlock, CodeRunnerPanel, CollapsibleSection, CommunityLinks, ConditionalWrapper, ConfettiEffect, ConfirmDialog, ConnectionBlock, Container, ContentRenderer, ContentSection, ControlButton, ControlGrid, CounterTemplate, DEFAULT_LIKERT_OPTIONS, DEFAULT_MATRIX_COLUMNS, DEFAULT_SLOTS, DIAMOND_TOP_Y, DashboardGrid, DashboardLayout, DataGrid, DataList, DataTable, DateRangePicker, DateRangeSelector, DayCell, DetailPanel, Dialog, DialogueBubble, Divider, DocBreadcrumb, DocPagination, DocSearch, DocSidebar, DocTOC, DocumentDetails, DocumentPanel, DocumentViewer, StateMachineView as DomStateMachineVisualizer, Drawer, DrawerSlot, ELEMENT_SELECTED_EVENT, EdgeDecoration, EditorCheckbox, EditorSelect, EditorSlider, EditorTextInput, EditorToolbar, EmojiPicker, EmptyState, EntityDisplayEvents, ErrorBoundary, ErrorState, FEATURE_COLORS, FEATURE_TYPES, FLOOR_HEIGHT, FeatureCard, FeatureDetailPageTemplate, FeatureGrid, FeatureGridOrganism, FileTree, FilterGroup, FilterPill, Flex, FlipCard, FlipContainer, FloatingActionButton, Form, FormActions, FormField, FormLayout, FormSection, FormSectionHeader, FxOverlay, GameAudioToggle, GameHud, GameIcon, GameMenu, GameShell, GenericAppTemplate, GeometricPattern, GradientDivider, GraphCanvas, GraphView, Grid, GridPicker, HStack, Header, HealthBar, HeroOrganism, HeroSection, I18nProvider, IDENTITY_BOOK_FIELDS, Icon, IconPicker, ImportPreviewTree, ImportProgress, ImportSourcePicker, InfiniteScrollSentinel, Input, InputGroup, InstallBox, JazariStateMachine, JsonTreeEditor, Label, LandingPageTemplate, LawReferenceTooltip, LearningCanvas, LearningScene3D, Lightbox, LikertScale, LineChart2 as LineChart, List3 as List, LoadingState, MapView, MarkdownContent, MarketingFooter, MarketingStatCard, MasterDetail, MasterDetailLayout, MathCanvas, MatrixQuestion, MediaGallery, Menu, Meter, Modal, ModalSlot, ModuleCard, Navigation, NodeSlotEditor, NotifyListener, NumberStepper, OnboardingSpotlight, OptionConstraintGroup, StateMachineView as OrbitalStateMachineView, OrbitalVisualization, Overlay, PageHeader, PageTransition, Pagination, PatternTile, PhysicsCanvas, Popover, PositionedCanvas, Presence, PricingCard, PricingGrid, PricingOrganism, PricingPageTemplate, ProgressBar, ProgressDots, PropertyInspector, PullQuote, PullToRefresh, QrScanner, QuizBlock, Radio, RangeSlider, ReflectionBlock, RelationSelect, RepeatableFormSection, ReplyTree, RichTextEditor, RuntimeDebugger, SHEET_COLUMNS, SPRITE_SHEET_LAYOUT, ScaledDiagram, ScoreDisplay, SearchInput, Section, SectionHeader, SegmentRenderer, Select, SequenceBar, ServiceCatalog, SharedEntityStoreContext, ShowcaseCard, ShowcaseOrganism, SidePanel, Sidebar, SignaturePad, SimpleGrid, Skeleton, SlotContentRenderer, SocialProof, SortableList, Spacer, Sparkline, Spinner, Split, SplitPane, SplitSection, Stack, StarRating, StatBadge, StatCard, StatDisplay, StateGraph, StateJsonView, StateMachineView, StateNode2 as StateNode, StatsGrid, StatsOrganism, StatusBar, StatusDot, StepFlow, StepFlowOrganism, SubagentTracePanel, SvgBranch, SvgConnection, SvgFlow, SvgGrid, SvgLobe, SvgMesh, SvgMorph, SvgNode, SvgPulse, SvgRing, SvgShield, SvgStack, SwipeableRow, Switch, TERRAIN_COLORS, TILE_HEIGHT, TILE_WIDTH, TabbedContainer, TableView, Tabs, TagCloud, TagInput, TeamCard, TeamOrganism, TerrainPalette, TextHighlight, Textarea, ThemeToggle, TimeSlotCell, Timeline, TimerDisplay, Toast, ToastSlot, Tooltip, TraitFrame, TraitSlot, TraitStateViewer, TransitionArrow, TrendIndicator, TypewriterText, Typography, UISlotComponent, UISlotRenderer, UploadDropZone, VStack, VersionDiff, ViolationAlert, VoteStack, WizardContainer, WizardNavigation, WizardProgress, arrowBetween, billboardLabel, boardEntity, bool, calculateAttackTargets, calculateValidMoves, cn, createInitialGameState, createSharedEntityStore, createTranslate, createUnitAnimationState, cylinderBetween, get3DClickPayload, getCurrentFrame, getTileDimensions, inferDirection, isoToScreen, makeAsset, makeAssetMap, mapBookData, meshSphere, num, objAvailableActions, objAvailableEvents, objCurrentState, objIcon, objId, objMaxRules, objName, objRules, objStates, parseEditFocus, parseLessonSegments, parseMarkdownWithCodeBlocks, parseQueryBinding, pendulum, projectileMotion, registerCodeLanguageLoader, renderPatternValue, resolveFieldMap, resolveFrame, resolveSheetDirection, rows, runTickFrame, sanitizeRichHtml, screenToIso, springOscillator, str, tickAnimationState, toCodeLanguage, transitionAnimation, unitHealth, unitPosition, unitTeam, useAgentChat, useAnchorRect, useAtlasSliceDataUrl, useAuthContext, useCamera, useCanvasGestures, useCompile, useConnectGitHub, useDeepAgentGeneration, useDisconnectGitHub, useDragReorder, useDraggable, useDropZone, useEmitEvent, useEventBus, useEventListener, useExtensions, useFileEditor, useFileSystem, useGameAudio, useGitHubBranches, useGitHubRepo, useGitHubRepos, useGitHubStatus, useImageCache, useInfiniteScroll, useLongPress, useMediaQuery, useOrbitalHistory, usePresence, usePreview, usePullToRefresh, useQuerySingleton, useRenderInterpolation, useSharedEntitySnapshot, useSharedEntityStore, useSharedEntityStoreContext, useSwipeGesture, useTapReveal, useTraitListens, useTranslate116 as useTranslate, useUIEvents, useUISlotManager, useUnitSpriteAtlas, useValidation, vec2 };