@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.
@@ -10,7 +10,7 @@ import { mergeEntityFrame, isCircuitEvent, applyListenPayloadMapping, schemaToIR
10
10
  import { clsx } from 'clsx';
11
11
  import { twMerge } from 'tailwind-merge';
12
12
  import * as LucideIcons2 from 'lucide-react';
13
- import { Loader2, X, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, Code, FileText, WrapText, Check, Copy, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ArrowLeft, ZoomOut, ZoomIn, Download, DollarSign, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, ChevronUp } from 'lucide-react';
13
+ import { Loader2, X, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, Code, FileText, WrapText, Check, Copy, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ArrowLeft, ZoomOut, ZoomIn, Download, DollarSign, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, HelpCircle, PauseCircle, Search, Bold, Italic, Underline, Strikethrough, Heading1, Heading2, Heading3, ListOrdered, Quote, Link as Link$1, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, ChevronUp } from 'lucide-react';
14
14
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
15
  import { createPortal } from 'react-dom';
16
16
  import { useTranslate } from '@almadar/ui/hooks';
@@ -25829,8 +25829,9 @@ function DataGrid({
25829
25829
  const titleField = fieldDefs.find((f3) => f3.variant === "h3" || f3.variant === "h4") ?? fieldDefs[0];
25830
25830
  const badgeFields = fieldDefs.filter((f3) => f3.variant === "badge" && f3 !== titleField);
25831
25831
  const bodyFields = fieldDefs.filter((f3) => f3 !== titleField && !badgeFields.includes(f3));
25832
- const primaryActions = actionDefs.filter((a) => a.variant !== "danger");
25833
- const dangerActions = actionDefs.filter((a) => a.variant === "danger");
25832
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
25833
+ const inlineCardActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
25834
+ const menuCardActions = actionDefs.filter((a) => !inlineCardActions.includes(a));
25834
25835
  const fireAction = (action, itemData) => {
25835
25836
  if (action.navigatesTo) {
25836
25837
  const url = action.navigatesTo.replace(
@@ -25850,6 +25851,9 @@ function DataGrid({
25850
25851
  e.stopPropagation();
25851
25852
  fireAction(action, itemData);
25852
25853
  };
25854
+ const cardClickAction = actionDefs.find((a) => a.variant !== "danger");
25855
+ const handleCardClick = cardClickAction ? (itemData) => () => fireAction(cardClickAction, itemData) : void 0;
25856
+ const stopCardClick = handleCardClick ? (e) => e.stopPropagation() : void 0;
25853
25857
  const hasRenderProp = typeof children === "function";
25854
25858
  useEffect(() => {
25855
25859
  if (data.length > 0 && !hasRenderProp && fieldDefs.length === 0) {
@@ -25921,55 +25925,40 @@ function DataGrid({
25921
25925
  {
25922
25926
  "data-entity-row": true,
25923
25927
  "data-entity-id": id,
25924
- className: cn("relative group/rowactions", isSelected && "ring-2 ring-primary rounded-lg"),
25928
+ onClick: handleCardClick?.(itemData),
25929
+ className: cn("relative group/rowactions", handleCardClick && "cursor-pointer", isSelected && "ring-2 ring-primary rounded-lg"),
25925
25930
  children: [
25926
25931
  children(itemData, index),
25927
- 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: [
25928
- /* @__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: [
25929
- (maxInlineActions != null ? actionDefs.slice(0, maxInlineActions) : actionDefs).map((action, idx) => /* @__PURE__ */ jsxs(
25930
- Button,
25931
- {
25932
- variant: action.variant === "primary" ? "primary" : "ghost",
25933
- size: "sm",
25934
- onClick: handleActionClick(action, itemData),
25935
- "data-testid": `action-${action.event}`,
25936
- "data-row-id": String(itemData.id),
25937
- className: action.variant === "danger" ? "text-error hover:text-error hover:bg-error/10" : void 0,
25938
- children: [
25939
- action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
25940
- action.label
25941
- ]
25942
- },
25943
- idx
25944
- )),
25945
- maxInlineActions != null && actionDefs.length > maxInlineActions && /* @__PURE__ */ jsx(
25946
- Menu,
25947
- {
25948
- position: "bottom-end",
25949
- 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" }) }),
25950
- items: actionDefs.slice(maxInlineActions).map((action) => ({
25951
- label: action.label,
25952
- icon: action.icon,
25953
- event: action.event,
25954
- onClick: () => fireAction(action, itemData)
25955
- }))
25956
- }
25957
- )
25958
- ] }),
25959
- /* @__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(
25932
+ 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: [
25933
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxs(
25934
+ Button,
25935
+ {
25936
+ variant: "primary",
25937
+ size: "sm",
25938
+ onClick: handleActionClick(action, itemData),
25939
+ "data-testid": `action-${action.event}`,
25940
+ "data-row-id": String(itemData.id),
25941
+ children: [
25942
+ action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
25943
+ action.label
25944
+ ]
25945
+ },
25946
+ idx
25947
+ )),
25948
+ menuCardActions.length > 0 && /* @__PURE__ */ jsx(
25960
25949
  Menu,
25961
25950
  {
25962
25951
  position: "bottom-end",
25963
25952
  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" }) }),
25964
- items: actionDefs.map((action) => ({
25953
+ items: menuCardActions.map((action) => ({
25965
25954
  label: action.label,
25966
25955
  icon: action.icon,
25967
- event: action.event,
25956
+ variant: action.variant === "danger" ? "danger" : "default",
25968
25957
  onClick: () => fireAction(action, itemData)
25969
25958
  }))
25970
25959
  }
25971
- ) })
25972
- ] })
25960
+ )
25961
+ ] }) })
25973
25962
  ]
25974
25963
  },
25975
25964
  id
@@ -25983,12 +25972,14 @@ function DataGrid({
25983
25972
  {
25984
25973
  "data-entity-row": true,
25985
25974
  "data-entity-id": id,
25975
+ onClick: handleCardClick?.(itemData),
25986
25976
  className: cn(
25987
25977
  "bg-card rounded-lg",
25988
25978
  "border border-border",
25989
25979
  "shadow-elevation-card hover:shadow-elevation-dialog",
25990
25980
  "hover:border-primary transition-all",
25991
25981
  "flex flex-col",
25982
+ handleCardClick && "cursor-pointer",
25992
25983
  isSelected && "ring-2 ring-primary border-primary"
25993
25984
  ),
25994
25985
  children: [
@@ -26038,53 +26029,35 @@ function DataGrid({
26038
26029
  ] }, field.name);
26039
26030
  }) })
26040
26031
  ] }),
26041
- (primaryActions.length > 0 || dangerActions.length > 0) && /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
26042
- (maxInlineActions != null ? primaryActions.slice(0, maxInlineActions) : primaryActions).map((action, idx) => /* @__PURE__ */ jsx(
26032
+ actionDefs.length > 0 && /* @__PURE__ */ jsxs(HStack, { gap: "xs", onClick: stopCardClick, className: "flex-shrink-0", children: [
26033
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsx(
26043
26034
  Button,
26044
26035
  {
26045
- variant: action.variant === "primary" ? "primary" : "ghost",
26036
+ variant: "primary",
26046
26037
  size: "sm",
26047
26038
  onClick: handleActionClick(action, itemData),
26048
26039
  "data-testid": `action-${action.event}`,
26049
26040
  "data-row-id": String(itemData.id),
26050
26041
  "aria-label": action.label,
26051
26042
  title: action.label,
26052
- className: cn(
26053
- action.variant === "primary" ? void 0 : "text-muted-foreground hover:text-foreground",
26054
- action.icon && "px-2"
26055
- ),
26043
+ className: cn(action.icon && "px-2"),
26056
26044
  children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
26057
26045
  },
26058
26046
  idx
26059
26047
  )),
26060
- maxInlineActions != null && primaryActions.length > maxInlineActions && /* @__PURE__ */ jsx(
26048
+ menuCardActions.length > 0 && /* @__PURE__ */ jsx(
26061
26049
  Menu,
26062
26050
  {
26063
26051
  position: "bottom-end",
26064
26052
  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" }) }),
26065
- items: primaryActions.slice(maxInlineActions).map((action) => ({
26053
+ items: menuCardActions.map((action) => ({
26066
26054
  label: action.label,
26067
26055
  icon: action.icon,
26068
- event: action.event,
26056
+ variant: action.variant === "danger" ? "danger" : "default",
26069
26057
  onClick: () => fireAction(action, itemData)
26070
26058
  }))
26071
26059
  }
26072
- ),
26073
- dangerActions.map((action, idx) => /* @__PURE__ */ jsx(
26074
- Button,
26075
- {
26076
- variant: "ghost",
26077
- size: "sm",
26078
- onClick: handleActionClick(action, itemData),
26079
- "data-testid": `action-${action.event}`,
26080
- "data-row-id": String(itemData.id),
26081
- "aria-label": action.label,
26082
- title: action.label,
26083
- className: "text-error hover:text-error hover:bg-error/10 px-2",
26084
- children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
26085
- },
26086
- `danger-${idx}`
26087
- ))
26060
+ )
26088
26061
  ] })
26089
26062
  ] }) }),
26090
26063
  bodyFields.length > 0 && /* @__PURE__ */ jsx(Box, { className: "px-4 pt-2 pb-4 flex-1", children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
@@ -26105,8 +26078,8 @@ function DataGrid({
26105
26078
  }
26106
26079
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
26107
26080
  field.icon && renderIconInput(field.icon, { size: "xs", className: "text-muted-foreground" }),
26108
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26109
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue(value, field.format) })
26081
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", className: "sr-only", children: (field.label ?? fieldLabel2(field.name)) + ":" }),
26082
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue(value, field.format) })
26110
26083
  ] }, field.name);
26111
26084
  }) })
26112
26085
  ] }) })
@@ -26326,54 +26299,59 @@ function DataList({
26326
26299
  if (!itemActions || itemActions.length === 0) return null;
26327
26300
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
26328
26301
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
26329
- return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
26330
- inline.map((action, idx) => /* @__PURE__ */ jsxs(
26331
- Button,
26332
- {
26333
- variant: action.variant ?? "ghost",
26334
- size: "sm",
26335
- onClick: handleActionClick(action, itemData),
26336
- "data-testid": `action-${action.event}`,
26337
- "data-row-id": String(itemData.id),
26338
- className: cn(
26339
- action.variant === "danger" && "text-error hover:bg-error/10",
26340
- // Must sit on the Button itself: the variant's own text colour
26341
- // beats an inherited one from the row wrapper.
26342
- onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
26343
- ),
26344
- children: [
26345
- action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
26346
- action.label
26347
- ]
26348
- },
26349
- idx
26350
- )),
26351
- overflow.length > 0 && /* @__PURE__ */ jsx(
26352
- Menu,
26353
- {
26354
- position: "bottom-end",
26355
- 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" }) }),
26356
- items: overflow.map((action) => ({
26357
- label: action.label,
26358
- icon: action.icon,
26359
- event: action.event,
26360
- variant: action.variant === "danger" ? "danger" : "default",
26361
- onClick: () => eventBus.emit(`UI:${action.event}`, {
26362
- id: itemData.id,
26363
- row: itemData
26364
- })
26365
- }))
26366
- }
26367
- )
26368
- ] });
26302
+ return (
26303
+ // stopPropagation at the cluster: the "⋯" trigger opens the overflow
26304
+ // menu without also firing the row's default click (inline buttons
26305
+ // already stop it in handleActionClick; the Menu panel is portaled).
26306
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, className: "flex-shrink-0", children: [
26307
+ inline.map((action, idx) => /* @__PURE__ */ jsxs(
26308
+ Button,
26309
+ {
26310
+ variant: action.variant ?? "ghost",
26311
+ size: "sm",
26312
+ onClick: handleActionClick(action, itemData),
26313
+ "data-testid": `action-${action.event}`,
26314
+ "data-row-id": String(itemData.id),
26315
+ className: cn(
26316
+ action.variant === "danger" && "text-error hover:bg-error/10",
26317
+ // Must sit on the Button itself: the variant's own text colour
26318
+ // beats an inherited one from the row wrapper.
26319
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
26320
+ ),
26321
+ children: [
26322
+ action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
26323
+ action.label
26324
+ ]
26325
+ },
26326
+ idx
26327
+ )),
26328
+ overflow.length > 0 && /* @__PURE__ */ jsx(
26329
+ Menu,
26330
+ {
26331
+ position: "bottom-end",
26332
+ 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" }) }),
26333
+ items: overflow.map((action) => ({
26334
+ label: action.label,
26335
+ icon: action.icon,
26336
+ variant: action.variant === "danger" ? "danger" : "default",
26337
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
26338
+ id: itemData.id,
26339
+ row: itemData
26340
+ })
26341
+ }))
26342
+ }
26343
+ )
26344
+ ] })
26345
+ );
26369
26346
  };
26347
+ const rowClickEvent = itemClickEvent || itemActions?.find((a) => a.variant !== "danger")?.event;
26370
26348
  const handleRowClick = (itemData) => () => {
26371
- if (!itemClickEvent) return;
26349
+ if (!rowClickEvent) return;
26372
26350
  const payload = {
26373
26351
  id: itemData.id,
26374
26352
  row: itemData
26375
26353
  };
26376
- eventBus.emit(`UI:${itemClickEvent}`, payload);
26354
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
26377
26355
  };
26378
26356
  if (isLoading) {
26379
26357
  return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
@@ -26421,10 +26399,10 @@ function DataList({
26421
26399
  {
26422
26400
  "data-entity-row": true,
26423
26401
  "data-entity-id": id,
26424
- onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
26402
+ onClick: rowClickEvent ? handleRowClick(itemData) : void 0,
26425
26403
  className: cn(
26426
26404
  "flex px-4 group/rowactions",
26427
- itemClickEvent && "cursor-pointer",
26405
+ rowClickEvent && "cursor-pointer",
26428
26406
  isSent ? "justify-end" : "justify-start"
26429
26407
  ),
26430
26408
  children: /* @__PURE__ */ jsxs(
@@ -26500,7 +26478,7 @@ function DataList({
26500
26478
  const id2 = itemData.id || String(index);
26501
26479
  const actions = renderItemActions(itemData);
26502
26480
  return wrapDnd(
26503
- /* @__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: [
26481
+ /* @__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: [
26504
26482
  children(itemData, index),
26505
26483
  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: [
26506
26484
  /* @__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 }),
@@ -26512,7 +26490,6 @@ function DataList({
26512
26490
  items: (itemActions ?? []).map((action) => ({
26513
26491
  label: action.label,
26514
26492
  icon: action.icon,
26515
- event: action.event,
26516
26493
  variant: action.variant === "danger" ? "danger" : "default",
26517
26494
  onClick: () => eventBus.emit(`UI:${action.event}`, {
26518
26495
  id: itemData.id,
@@ -26529,12 +26506,16 @@ function DataList({
26529
26506
  const id = itemData.id || String(index);
26530
26507
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
26531
26508
  return wrapDnd(
26532
- /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn(itemClickEvent && "cursor-pointer"), children: [
26509
+ /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
26533
26510
  /* @__PURE__ */ jsxs(
26534
26511
  Box,
26535
26512
  {
26536
26513
  className: cn(
26537
- "group flex items-center gap-4 transition-all duration-fast",
26514
+ // items-start, not items-center: a multi-line row (title + meta +
26515
+ // progress) centred its action cluster in the vertical middle, so
26516
+ // the buttons floated between the meta fields instead of anchoring
26517
+ // to the title they act on (U-DATALIST-ACTIONS-FLOAT-MID-ROW).
26518
+ "group flex items-start gap-4 transition-all duration-fast",
26538
26519
  isCompact ? "px-4 py-2" : "px-6 py-4",
26539
26520
  "hover:bg-muted/80",
26540
26521
  !isCard && !isCompact && "rounded-lg border border-transparent hover:border-border"
@@ -26565,11 +26546,19 @@ function DataList({
26565
26546
  if (value === void 0 || value === null || value === "") return null;
26566
26547
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
26567
26548
  field.icon && renderIconInput2(field.icon, { size: "xs", className: "text-muted-foreground" }),
26568
- /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
26569
- field.label ?? fieldLabel3(field.name),
26570
- ":"
26571
- ] }),
26572
- /* @__PURE__ */ jsx(Typography, { variant: "small", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
26549
+ /* @__PURE__ */ jsxs(
26550
+ Typography,
26551
+ {
26552
+ variant: "caption",
26553
+ color: "secondary",
26554
+ className: cn(field.format !== "boolean" && "sr-only"),
26555
+ children: [
26556
+ field.label ?? fieldLabel3(field.name),
26557
+ ":"
26558
+ ]
26559
+ }
26560
+ ),
26561
+ /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: formatValue2(value, field.format, { yes: t("common.yes"), no: t("common.no") }) })
26573
26562
  ] }, field.name);
26574
26563
  }) }),
26575
26564
  progressFields.map((field) => {
@@ -26588,7 +26577,7 @@ function DataList({
26588
26577
  ]
26589
26578
  }
26590
26579
  ),
26591
- isCard && !isLast && /* @__PURE__ */ jsx(Box, { className: "mx-6 border-b border-border/40" })
26580
+ (isCard || isCompact) && !isLast && /* @__PURE__ */ jsx(Box, { className: cn("border-b border-border/40", isCompact ? "mx-4" : "mx-6") })
26592
26581
  ] }, id)
26593
26582
  );
26594
26583
  };
@@ -26598,7 +26587,13 @@ function DataList({
26598
26587
  {
26599
26588
  className: cn(
26600
26589
  isCard && "bg-card rounded-xl border border-border shadow-elevation-dialog overflow-hidden",
26601
- !isCard && gapClass,
26590
+ // `gap-*` is inert on a block container, and Box only emits a display
26591
+ // class when its `display` prop is set — so every non-card list had
26592
+ // been asking for a gap that CSS silently dropped. flex-col makes it
26593
+ // real. `compact` keeps gap-0 on purpose: it separates with the row
26594
+ // divider above instead, never both (Almadar_UI_Beauty.md 6).
26595
+ !isCard && "flex flex-col",
26596
+ !isCard && !isCompact && gapClass,
26602
26597
  listLookStyles[look],
26603
26598
  className
26604
26599
  ),
@@ -31723,7 +31718,7 @@ var init_MapView = __esm({
31723
31718
  shadowSize: [41, 41]
31724
31719
  });
31725
31720
  L.Marker.prototype.options.icon = defaultIcon;
31726
- const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback90, useState: useState92 } = React85__default;
31721
+ const { useEffect: useEffect63, useRef: useRef63, useCallback: useCallback90, useState: useState94 } = React85__default;
31727
31722
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
31728
31723
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
31729
31724
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -31768,7 +31763,7 @@ var init_MapView = __esm({
31768
31763
  showAttribution = true
31769
31764
  }) {
31770
31765
  const eventBus = useEventBus2();
31771
- const [clickedPosition, setClickedPosition] = useState92(null);
31766
+ const [clickedPosition, setClickedPosition] = useState94(null);
31772
31767
  const handleMapClick = useCallback90((lat, lng) => {
31773
31768
  if (showClickedPin) {
31774
31769
  setClickedPosition({ lat, lng });
@@ -35992,741 +35987,775 @@ var init_PositionedCanvas = __esm({
35992
35987
  PositionedCanvas.displayName = "PositionedCanvas";
35993
35988
  }
35994
35989
  });
35995
- function nextBlockId(prefix = "blk") {
35996
- _idSeq += 1;
35997
- const random = Math.random().toString(36).slice(2, 8);
35998
- return `${prefix}-${Date.now().toString(36)}-${_idSeq}-${random}`;
35999
- }
36000
- function normalizeBlocks(raw) {
36001
- if (!Array.isArray(raw) || raw.length === 0) return [createBlock("paragraph")];
36002
- return raw.map((row) => {
36003
- const entity = row;
36004
- const rawType = entity.type;
36005
- const type = typeof rawType === "string" && BLOCK_TYPES.has(rawType) ? rawType : "paragraph";
36006
- const id = typeof entity.id === "string" && entity.id ? entity.id : nextBlockId(type);
36007
- return { ...entity, id, type };
36008
- });
36009
- }
36010
- function createBlock(type) {
36011
- switch (type) {
36012
- case "bullet-list":
36013
- case "numbered-list":
36014
- return {
36015
- id: nextBlockId(type),
36016
- type,
36017
- children: [
36018
- { id: nextBlockId("li"), type: "paragraph", content: "" }
36019
- ]
36020
- };
36021
- case "image":
36022
- return {
36023
- id: nextBlockId(type),
36024
- type,
36025
- content: "",
36026
- metadata: { url: "", caption: "" }
36027
- };
36028
- case "code":
36029
- return {
36030
- id: nextBlockId(type),
36031
- type,
36032
- content: "",
36033
- metadata: { language: "plaintext" }
36034
- };
36035
- case "divider":
36036
- return { id: nextBlockId(type), type };
36037
- default:
36038
- return { id: nextBlockId(type), type, content: "" };
35990
+ function safeUrl(raw, kinds) {
35991
+ const url = raw.trim();
35992
+ if (url.startsWith("#") || url.startsWith("/")) return url;
35993
+ const lower = url.toLowerCase();
35994
+ for (const kind of kinds) {
35995
+ if (lower.startsWith(kind)) return url;
36039
35996
  }
35997
+ return null;
36040
35998
  }
36041
- function replaceBlock(blocks, id, updater) {
36042
- return blocks.map((block) => block.id === id ? updater(block) : block);
36043
- }
36044
- function removeBlock(blocks, id) {
36045
- return blocks.filter((block) => block.id !== id);
36046
- }
36047
- function duplicateBlock(block) {
36048
- return {
36049
- ...block,
36050
- id: nextBlockId(block.type),
36051
- children: block.children?.map((child) => ({
36052
- ...child,
36053
- id: nextBlockId("li")
36054
- })),
36055
- metadata: block.metadata ? { ...block.metadata } : void 0
36056
- };
36057
- }
36058
- function insertAfter(blocks, targetId, inserted) {
36059
- const idx = blocks.findIndex((b) => b.id === targetId);
36060
- if (idx === -1) return [...blocks, inserted];
36061
- const next = blocks.slice();
36062
- next.splice(idx + 1, 0, inserted);
36063
- return next;
36064
- }
36065
- function changeBlockType(block, type) {
36066
- if (block.type === type) return block;
36067
- if (type === "bullet-list" || type === "numbered-list") {
36068
- if (block.children && block.children.length > 0) {
36069
- return { ...block, type };
35999
+ function sanitizeNode(el) {
36000
+ const children = Array.from(el.children);
36001
+ for (const child of children) {
36002
+ const tag = child.tagName.toLowerCase();
36003
+ if (DROP_TAGS.has(tag)) {
36004
+ child.remove();
36005
+ continue;
36070
36006
  }
36071
- const seed2 = block.content ?? "";
36072
- return {
36073
- id: block.id,
36074
- type,
36075
- children: [
36076
- { id: nextBlockId("li"), type: "paragraph", content: seed2 }
36077
- ]
36078
- };
36079
- }
36080
- if (type === "divider") {
36081
- return { id: block.id, type };
36007
+ const allowed = ALLOWED_ATTRS[tag];
36008
+ if (!allowed) {
36009
+ sanitizeNode(child);
36010
+ child.replaceWith(...Array.from(child.childNodes));
36011
+ continue;
36012
+ }
36013
+ for (const attr of Array.from(child.attributes)) {
36014
+ const name = attr.name.toLowerCase();
36015
+ if (!allowed.includes(name)) {
36016
+ child.removeAttribute(attr.name);
36017
+ continue;
36018
+ }
36019
+ if (name === "href") {
36020
+ const url = safeUrl(attr.value, ["http://", "https://", "mailto:"]);
36021
+ if (url === null) child.removeAttribute(attr.name);
36022
+ else child.setAttribute("href", url);
36023
+ }
36024
+ if (name === "src") {
36025
+ const url = safeUrl(attr.value, ["http://", "https://", "data:image/"]);
36026
+ if (url === null) child.remove();
36027
+ else child.setAttribute("src", url);
36028
+ }
36029
+ }
36030
+ sanitizeNode(child);
36082
36031
  }
36083
- if (type === "image") {
36084
- return {
36085
- id: block.id,
36086
- type,
36087
- content: "",
36088
- metadata: { url: "", caption: block.content ?? "" }
36089
- };
36032
+ }
36033
+ function sanitizeRichHtml(html) {
36034
+ if (!html || typeof html !== "string") return "";
36035
+ if (typeof window === "undefined" || typeof DOMParser === "undefined") return "";
36036
+ const doc = new DOMParser().parseFromString(html, "text/html");
36037
+ sanitizeNode(doc.body);
36038
+ if (!doc.body.querySelector("img, hr") && (doc.body.textContent ?? "").trim().length === 0) {
36039
+ return "";
36090
36040
  }
36091
- if (type === "code") {
36092
- return {
36093
- id: block.id,
36094
- type,
36095
- content: block.content ?? "",
36096
- metadata: { language: "plaintext" }
36097
- };
36041
+ return doc.body.innerHTML.replace(/​/g, "");
36042
+ }
36043
+ function htmlIsEmpty(el) {
36044
+ if (el.querySelector("img, hr")) return false;
36045
+ return (el.textContent ?? "").trim().length === 0;
36046
+ }
36047
+ function closestBlock(node, root) {
36048
+ let cur = node;
36049
+ while (cur && cur !== root) {
36050
+ if (cur instanceof Element && BLOCK_TAGS.has(cur.tagName)) return cur;
36051
+ cur = cur.parentNode;
36098
36052
  }
36099
- const seed = block.children?.[0]?.content ?? block.content ?? "";
36100
- return { id: block.id, type, content: seed };
36053
+ return null;
36101
36054
  }
36102
- function BlockMenu({ block, readOnly, onDelete, onDuplicate, onChangeType }) {
36103
- const { t } = useTranslate();
36104
- const [open, setOpen] = useState(false);
36105
- const ref = useRef(null);
36106
- useEffect(() => {
36107
- if (!open) return;
36108
- function onDocClick(e) {
36109
- if (ref.current && !ref.current.contains(e.target)) {
36110
- setOpen(false);
36055
+ function RichTextStyles() {
36056
+ return /* @__PURE__ */ jsx("style", { children: RICH_TEXT_CSS });
36057
+ }
36058
+ function readToolbarState(root) {
36059
+ const sel = window.getSelection();
36060
+ if (!sel || sel.rangeCount === 0) return IDLE_TOOLBAR;
36061
+ const anchor = sel.anchorNode;
36062
+ if (!anchor || !root.contains(anchor)) return IDLE_TOOLBAR;
36063
+ let block = null;
36064
+ let link = false;
36065
+ let node = anchor;
36066
+ while (node && node !== root) {
36067
+ if (node instanceof Element) {
36068
+ const tag = node.tagName.toLowerCase();
36069
+ if (!block && (tag === "h1" || tag === "h2" || tag === "h3" || tag === "blockquote" || tag === "pre")) {
36070
+ block = tag;
36111
36071
  }
36072
+ if (tag === "a") link = true;
36112
36073
  }
36113
- document.addEventListener("mousedown", onDocClick);
36114
- return () => document.removeEventListener("mousedown", onDocClick);
36115
- }, [open]);
36116
- if (readOnly) return null;
36117
- return /* @__PURE__ */ jsxs(Box, { ref, className: "relative", children: [
36118
- /* @__PURE__ */ jsx(
36119
- Button,
36120
- {
36121
- type: "button",
36122
- variant: "ghost",
36123
- "aria-label": t("richBlockEditor.blockActions"),
36124
- className: cn(
36125
- "inline-flex items-center justify-center",
36126
- "h-6 w-6 rounded-sm p-0 gap-0",
36127
- "text-muted-foreground hover:bg-muted hover:text-foreground",
36128
- "opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
36129
- "transition-opacity"
36130
- ),
36131
- onClick: () => setOpen((v) => !v),
36132
- children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", className: "w-3.5 h-3.5" })
36133
- }
36134
- ),
36135
- open && /* @__PURE__ */ jsxs(
36136
- Box,
36137
- {
36138
- role: "menu",
36139
- className: cn(
36140
- "absolute right-0 z-10 mt-1 w-44",
36141
- "rounded-container border border-border bg-popover shadow-elevation-popover",
36142
- "py-1 text-sm"
36143
- ),
36144
- children: [
36145
- /* @__PURE__ */ jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t(BLOCK_TYPE_LABEL_KEY[block.type]) }),
36146
- /* @__PURE__ */ jsxs(
36147
- Button,
36148
- {
36149
- type: "button",
36150
- variant: "ghost",
36151
- role: "menuitem",
36152
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
36153
- onClick: () => {
36154
- onDuplicate();
36155
- setOpen(false);
36156
- },
36157
- children: [
36158
- /* @__PURE__ */ jsx(Icon, { name: "plus", className: "w-3.5 h-3.5" }),
36159
- " ",
36160
- t("richBlockEditor.duplicate")
36161
- ]
36162
- }
36163
- ),
36164
- /* @__PURE__ */ jsxs(
36165
- Button,
36166
- {
36167
- type: "button",
36168
- variant: "ghost",
36169
- role: "menuitem",
36170
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-destructive hover:bg-muted justify-start rounded-none",
36171
- onClick: () => {
36172
- onDelete();
36173
- setOpen(false);
36174
- },
36175
- children: [
36176
- /* @__PURE__ */ jsx(Icon, { name: "trash", className: "w-3.5 h-3.5" }),
36177
- " ",
36178
- t("common.delete")
36179
- ]
36180
- }
36181
- ),
36182
- CHANGEABLE_TYPES.includes(block.type) && /* @__PURE__ */ jsxs(Fragment, { children: [
36183
- /* @__PURE__ */ jsx(Box, { className: "my-1 border-t border-border" }),
36184
- /* @__PURE__ */ jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t("richBlockEditor.turnInto") }),
36185
- CHANGEABLE_TYPES.filter((bt) => bt !== block.type).map((bt) => /* @__PURE__ */ jsx(
36186
- Button,
36187
- {
36188
- type: "button",
36189
- variant: "ghost",
36190
- role: "menuitem",
36191
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
36192
- onClick: () => {
36193
- onChangeType(bt);
36194
- setOpen(false);
36195
- },
36196
- children: t(BLOCK_TYPE_LABEL_KEY[bt])
36197
- },
36198
- bt
36199
- ))
36200
- ] })
36201
- ]
36202
- }
36203
- )
36204
- ] });
36074
+ node = node.parentNode;
36075
+ }
36076
+ return {
36077
+ bold: document.queryCommandState("bold"),
36078
+ italic: document.queryCommandState("italic"),
36079
+ underline: document.queryCommandState("underline"),
36080
+ strikeThrough: document.queryCommandState("strikeThrough"),
36081
+ block,
36082
+ bullets: document.queryCommandState("insertUnorderedList"),
36083
+ numbers: document.queryCommandState("insertOrderedList"),
36084
+ link
36085
+ };
36205
36086
  }
36206
- function Editable({
36207
- tag,
36208
- value,
36209
- readOnly,
36210
- placeholder,
36211
- className,
36212
- ariaLabel,
36213
- onValueChange
36214
- }) {
36215
- const ref = useRef(null);
36216
- useEffect(() => {
36217
- const el = ref.current;
36218
- if (!el) return;
36219
- const isFocused = document.activeElement === el;
36220
- if (!isFocused && el.textContent !== value) {
36221
- el.textContent = value;
36222
- }
36223
- }, [value]);
36224
- const handleInput = useCallback(
36225
- (e) => {
36226
- onValueChange(e.currentTarget.textContent ?? "");
36227
- },
36228
- [onValueChange]
36229
- );
36087
+ function ToolbarButton({ icon: IconCmp, label, active, onExec }) {
36230
36088
  return /* @__PURE__ */ jsx(
36231
- Box,
36089
+ Button,
36232
36090
  {
36233
- as: tag,
36234
- ref,
36235
- contentEditable: !readOnly,
36236
- suppressContentEditableWarning: true,
36237
- role: readOnly ? void 0 : "textbox",
36238
- "aria-label": ariaLabel,
36239
- "aria-multiline": "true",
36240
- "data-placeholder": placeholder,
36091
+ type: "button",
36092
+ variant: "ghost",
36093
+ size: "sm",
36094
+ "aria-label": label,
36095
+ "aria-pressed": active,
36096
+ title: label,
36241
36097
  className: cn(
36242
- "outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-sm",
36243
- "empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground/60",
36244
- className
36098
+ "h-8 w-8 p-0 gap-0 justify-center",
36099
+ active && "bg-muted text-foreground"
36245
36100
  ),
36246
- onInput: handleInput
36101
+ onMouseDown: (e) => {
36102
+ e.preventDefault();
36103
+ onExec();
36104
+ },
36105
+ children: /* @__PURE__ */ jsx(IconCmp, { size: 15 })
36247
36106
  }
36248
36107
  );
36249
36108
  }
36250
- function BlockRow({
36251
- block,
36252
- readOnly,
36253
- showAffordances,
36254
- placeholder,
36255
- onUpdate,
36256
- onDelete,
36257
- onDuplicate,
36258
- onChangeType
36259
- }) {
36260
- const { t } = useTranslate();
36261
- const setContent = useCallback(
36262
- (next) => onUpdate((b) => ({ ...b, content: next })),
36263
- [onUpdate]
36264
- );
36265
- const setMetadata = useCallback(
36266
- (key, value) => onUpdate((b) => ({
36267
- ...b,
36268
- metadata: { ...b.metadata ?? {}, [key]: value }
36269
- })),
36270
- [onUpdate]
36271
- );
36272
- const setChildContent = useCallback(
36273
- (childId, next) => onUpdate((b) => ({
36274
- ...b,
36275
- children: (b.children ?? []).map(
36276
- (c) => c.id === childId ? { ...c, content: next } : c
36277
- )
36278
- })),
36279
- [onUpdate]
36280
- );
36281
- const addListItem = useCallback(
36282
- () => onUpdate((b) => ({
36283
- ...b,
36284
- children: [
36285
- ...b.children ?? [],
36286
- { id: nextBlockId("li"), type: "paragraph", content: "" }
36287
- ]
36288
- })),
36289
- [onUpdate]
36290
- );
36291
- const removeListItem = useCallback(
36292
- (childId) => onUpdate((b) => {
36293
- const remaining = (b.children ?? []).filter((c) => c.id !== childId);
36294
- return {
36295
- ...b,
36296
- children: remaining.length === 0 ? [{ id: nextBlockId("li"), type: "paragraph", content: "" }] : remaining
36297
- };
36298
- }),
36299
- [onUpdate]
36300
- );
36301
- const renderBody = () => {
36302
- switch (block.type) {
36303
- case "heading-1":
36304
- return /* @__PURE__ */ jsx(
36305
- Editable,
36306
- {
36307
- tag: "h1",
36308
- value: block.content ?? "",
36309
- readOnly,
36310
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading1"),
36311
- ariaLabel: t("richBlockEditor.aria.heading1Block"),
36312
- className: "text-3xl font-bold leading-tight",
36313
- onValueChange: setContent
36314
- }
36315
- );
36316
- case "heading-2":
36317
- return /* @__PURE__ */ jsx(
36318
- Editable,
36319
- {
36320
- tag: "h2",
36321
- value: block.content ?? "",
36322
- readOnly,
36323
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading2"),
36324
- ariaLabel: t("richBlockEditor.aria.heading2Block"),
36325
- className: "text-2xl font-semibold leading-tight",
36326
- onValueChange: setContent
36327
- }
36328
- );
36329
- case "heading-3":
36330
- return /* @__PURE__ */ jsx(
36331
- Editable,
36332
- {
36333
- tag: "h3",
36334
- value: block.content ?? "",
36335
- readOnly,
36336
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading3"),
36337
- ariaLabel: t("richBlockEditor.aria.heading3Block"),
36338
- className: "text-xl font-semibold leading-tight",
36339
- onValueChange: setContent
36340
- }
36341
- );
36342
- case "quote":
36343
- return /* @__PURE__ */ jsx(
36344
- Editable,
36345
- {
36346
- tag: "blockquote",
36347
- value: block.content ?? "",
36348
- readOnly,
36349
- placeholder: placeholder ?? t("richBlockEditor.placeholder.quote"),
36350
- ariaLabel: t("richBlockEditor.aria.quoteBlock"),
36351
- className: "border-l-4 border-primary/60 pl-4 italic text-muted-foreground",
36352
- onValueChange: setContent
36353
- }
36354
- );
36355
- case "code":
36356
- return /* @__PURE__ */ jsxs(Box, { className: "rounded-md border border-border bg-muted/40", children: [
36357
- /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between border-b border-border px-3 py-1 text-xs text-muted-foreground", children: [
36358
- /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "uppercase tracking-wide", children: t("richBlockEditor.blockType.code") }),
36359
- !readOnly && /* @__PURE__ */ jsx(
36360
- Input,
36361
- {
36362
- inputType: "text",
36363
- value: String(block.metadata?.language ?? "plaintext"),
36364
- "aria-label": t("richBlockEditor.aria.codeLanguage"),
36365
- className: cn(
36366
- "h-6 w-32 rounded-sm border border-border bg-background",
36367
- "px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
36368
- ),
36369
- onChange: (e) => setMetadata("language", e.target.value)
36370
- }
36371
- ),
36372
- readOnly && /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "text-xs", children: String(block.metadata?.language ?? "plaintext") })
36373
- ] }),
36374
- /* @__PURE__ */ jsx(
36375
- Editable,
36376
- {
36377
- tag: "pre",
36378
- value: block.content ?? "",
36379
- readOnly,
36380
- placeholder: placeholder ?? t("richBlockEditor.placeholder.code"),
36381
- ariaLabel: t("richBlockEditor.aria.codeBlock"),
36382
- className: "block whitespace-pre-wrap p-3 font-mono text-sm leading-relaxed",
36383
- onValueChange: setContent
36384
- }
36385
- )
36386
- ] });
36387
- case "divider":
36388
- return /* @__PURE__ */ jsx(Divider, { className: "my-2" });
36389
- case "image": {
36390
- const url = String(block.metadata?.url ?? "");
36391
- const caption = String(block.metadata?.caption ?? "");
36392
- const imgProps = {
36393
- src: url,
36394
- alt: caption || t("richBlockEditor.embeddedImage"),
36395
- className: "max-h-96 w-full rounded-md border border-border object-contain"
36109
+ var CHANGE_DEBOUNCE_MS, ALLOWED_ATTRS, DROP_TAGS, MD_BLOCK_PREFIXES, BLOCK_TAGS, MD_INLINE_RULES, RICH_TEXT_CSS, IDLE_TOOLBAR, RichTextEditor;
36110
+ var init_RichTextEditor = __esm({
36111
+ "components/core/molecules/RichTextEditor.tsx"() {
36112
+ "use client";
36113
+ init_cn();
36114
+ init_Box();
36115
+ init_Button();
36116
+ init_useEventBus();
36117
+ CHANGE_DEBOUNCE_MS = 400;
36118
+ ALLOWED_ATTRS = {
36119
+ p: [],
36120
+ h1: [],
36121
+ h2: [],
36122
+ h3: [],
36123
+ h4: [],
36124
+ ul: [],
36125
+ ol: [],
36126
+ li: [],
36127
+ blockquote: [],
36128
+ pre: [],
36129
+ code: [],
36130
+ b: [],
36131
+ strong: [],
36132
+ i: [],
36133
+ em: [],
36134
+ u: [],
36135
+ s: [],
36136
+ strike: [],
36137
+ br: [],
36138
+ hr: [],
36139
+ div: [],
36140
+ span: [],
36141
+ a: ["href"],
36142
+ img: ["src", "alt"]
36143
+ };
36144
+ DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "link", "meta", "form", "input", "button", "textarea", "select"]);
36145
+ MD_BLOCK_PREFIXES = {
36146
+ "#": { kind: "block", tag: "h1" },
36147
+ "##": { kind: "block", tag: "h2" },
36148
+ "###": { kind: "block", tag: "h3" },
36149
+ ">": { kind: "block", tag: "blockquote" },
36150
+ "```": { kind: "block", tag: "pre" },
36151
+ "-": { kind: "list", command: "insertUnorderedList" },
36152
+ "*": { kind: "list", command: "insertUnorderedList" },
36153
+ "1.": { kind: "list", command: "insertOrderedList" }
36154
+ };
36155
+ BLOCK_TAGS = /* @__PURE__ */ new Set(["P", "DIV", "LI", "H1", "H2", "H3", "H4", "BLOCKQUOTE", "PRE"]);
36156
+ MD_INLINE_RULES = [
36157
+ { pattern: /\*\*([^*\n]+)\*\*$/, wrap: "strong" },
36158
+ { pattern: /(^|[^*])\*([^*\n]+)\*$/, wrap: "em" },
36159
+ { pattern: /`([^`\n]+)`$/, wrap: "code" }
36160
+ ];
36161
+ RICH_TEXT_CSS = `
36162
+ .almadar-rich-text { line-height: 1.7; color: var(--color-foreground); }
36163
+ .almadar-rich-text h1 { font-size: 1.875rem; font-weight: 700; line-height: 1.25; margin: 1.25em 0 0.4em; }
36164
+ .almadar-rich-text h2 { font-size: 1.5rem; font-weight: 650; line-height: 1.3; margin: 1.1em 0 0.4em; }
36165
+ .almadar-rich-text h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.35; margin: 1em 0 0.35em; }
36166
+ .almadar-rich-text h1:first-child, .almadar-rich-text h2:first-child, .almadar-rich-text h3:first-child { margin-top: 0; }
36167
+ .almadar-rich-text p { margin: 0.5em 0; }
36168
+ .almadar-rich-text ul { list-style: disc; padding-inline-start: 1.5rem; margin: 0.5em 0; }
36169
+ .almadar-rich-text ol { list-style: decimal; padding-inline-start: 1.5rem; margin: 0.5em 0; }
36170
+ .almadar-rich-text li { margin: 0.25em 0; }
36171
+ .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; }
36172
+ .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; }
36173
+ .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; }
36174
+ .almadar-rich-text pre code { background: transparent; padding: 0; }
36175
+ .almadar-rich-text a { color: var(--color-primary); text-decoration: underline; }
36176
+ .almadar-rich-text hr { border: none; border-top: 1px solid var(--color-border); margin: 1.25em 0; }
36177
+ .almadar-rich-text img { max-width: 100%; border-radius: 0.375rem; }
36178
+ `;
36179
+ IDLE_TOOLBAR = {
36180
+ bold: false,
36181
+ italic: false,
36182
+ underline: false,
36183
+ strikeThrough: false,
36184
+ block: null,
36185
+ bullets: false,
36186
+ numbers: false,
36187
+ link: false
36188
+ };
36189
+ RichTextEditor = ({
36190
+ value,
36191
+ onChange,
36192
+ changeEvent,
36193
+ readOnly = false,
36194
+ placeholder,
36195
+ showToolbar = true,
36196
+ className
36197
+ }) => {
36198
+ const { t } = useTranslate();
36199
+ const ref = useRef(null);
36200
+ const [toolbar, setToolbar] = useState(IDLE_TOOLBAR);
36201
+ const [empty, setEmpty] = useState(() => !value || !sanitizeRichHtml(value).trim());
36202
+ const eventBus = useEventBus();
36203
+ const onChangeRef = useRef(onChange);
36204
+ const changeEventRef = useRef(changeEvent);
36205
+ useEffect(() => {
36206
+ onChangeRef.current = onChange;
36207
+ changeEventRef.current = changeEvent;
36208
+ }, [onChange, changeEvent]);
36209
+ const emitTimerRef = useRef(null);
36210
+ const emitNow = useCallback(() => {
36211
+ const el = ref.current;
36212
+ if (!el) return;
36213
+ const html = sanitizeRichHtml(el.innerHTML);
36214
+ onChangeRef.current?.(html);
36215
+ const evt = changeEventRef.current;
36216
+ if (evt) eventBus.emit(`UI:${evt}`, { value: html });
36217
+ }, [eventBus]);
36218
+ const scheduleEmit = useCallback(() => {
36219
+ if (emitTimerRef.current) clearTimeout(emitTimerRef.current);
36220
+ emitTimerRef.current = setTimeout(() => {
36221
+ emitTimerRef.current = null;
36222
+ emitNow();
36223
+ }, CHANGE_DEBOUNCE_MS);
36224
+ }, [emitNow]);
36225
+ const flushEmit = useCallback(() => {
36226
+ if (!emitTimerRef.current) return;
36227
+ clearTimeout(emitTimerRef.current);
36228
+ emitTimerRef.current = null;
36229
+ emitNow();
36230
+ }, [emitNow]);
36231
+ useEffect(() => () => {
36232
+ if (emitTimerRef.current) {
36233
+ clearTimeout(emitTimerRef.current);
36234
+ emitTimerRef.current = null;
36235
+ emitNow();
36236
+ }
36237
+ }, [emitNow]);
36238
+ useEffect(() => {
36239
+ if (readOnly) return;
36240
+ const el = ref.current;
36241
+ if (!el) return;
36242
+ if (document.activeElement === el) return;
36243
+ const next = sanitizeRichHtml(value ?? "") || "<p><br></p>";
36244
+ if (el.innerHTML !== next) {
36245
+ el.innerHTML = next;
36246
+ setEmpty(htmlIsEmpty(el));
36247
+ }
36248
+ }, [readOnly, value]);
36249
+ useEffect(() => {
36250
+ if (readOnly) return;
36251
+ document.execCommand("defaultParagraphSeparator", false, "p");
36252
+ }, [readOnly]);
36253
+ useEffect(() => {
36254
+ if (readOnly || !showToolbar) return;
36255
+ const onSelectionChange = () => {
36256
+ const el = ref.current;
36257
+ if (!el) return;
36258
+ setToolbar(readToolbarState(el));
36396
36259
  };
36397
- return /* @__PURE__ */ jsxs(Box, { className: "space-y-2", children: [
36398
- url ? /* @__PURE__ */ jsx(Box, { as: "img", ...imgProps }) : /* @__PURE__ */ jsxs(
36399
- Box,
36400
- {
36401
- className: cn(
36402
- "flex h-32 items-center justify-center",
36403
- "rounded-md border border-dashed border-border",
36404
- "text-sm text-muted-foreground"
36405
- ),
36406
- children: [
36407
- /* @__PURE__ */ jsx(Icon, { name: "image", className: "mr-2 w-4 h-4" }),
36408
- " ",
36409
- t("richBlockEditor.noImageUrl")
36410
- ]
36411
- }
36412
- ),
36413
- !readOnly && /* @__PURE__ */ jsxs(Box, { className: "flex flex-col gap-2 sm:flex-row", children: [
36414
- /* @__PURE__ */ jsx(
36415
- Input,
36416
- {
36417
- inputType: "url",
36418
- value: url,
36419
- placeholder: "https://example.com/image.png",
36420
- "aria-label": t("richBlockEditor.aria.imageUrl"),
36421
- className: cn(
36422
- "h-8 flex-1 rounded-sm border border-border bg-background",
36423
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
36424
- ),
36425
- onChange: (e) => setMetadata("url", e.target.value)
36426
- }
36427
- ),
36428
- /* @__PURE__ */ jsx(
36429
- Input,
36430
- {
36431
- inputType: "text",
36432
- value: caption,
36433
- placeholder: t("richBlockEditor.placeholder.caption"),
36434
- "aria-label": t("richBlockEditor.aria.imageCaption"),
36435
- className: cn(
36436
- "h-8 flex-1 rounded-sm border border-border bg-background",
36437
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
36438
- ),
36439
- onChange: (e) => setMetadata("caption", e.target.value)
36440
- }
36441
- )
36442
- ] }),
36443
- readOnly && caption && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-center text-muted-foreground", children: caption })
36260
+ document.addEventListener("selectionchange", onSelectionChange);
36261
+ return () => document.removeEventListener("selectionchange", onSelectionChange);
36262
+ }, [readOnly, showToolbar]);
36263
+ const tryInlineMarkdown = useCallback(() => {
36264
+ const root = ref.current;
36265
+ if (!root) return false;
36266
+ const sel = window.getSelection();
36267
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
36268
+ const node = sel.anchorNode;
36269
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
36270
+ if (node.parentElement?.closest("code, pre")) return false;
36271
+ const text = (node.textContent ?? "").slice(0, sel.anchorOffset);
36272
+ for (const rule of MD_INLINE_RULES) {
36273
+ const m = rule.pattern.exec(text);
36274
+ if (!m) continue;
36275
+ const inner = rule.wrap === "em" ? m[2] : m[1];
36276
+ if (!inner || !inner.trim()) continue;
36277
+ const lead = rule.wrap === "em" ? m[1]?.length ?? 0 : 0;
36278
+ const range = document.createRange();
36279
+ range.setStart(node, m.index + lead);
36280
+ range.setEnd(node, sel.anchorOffset);
36281
+ const el = document.createElement(rule.wrap);
36282
+ el.textContent = inner;
36283
+ range.deleteContents();
36284
+ range.insertNode(el);
36285
+ const after = document.createTextNode("\u200B");
36286
+ el.parentNode?.insertBefore(after, el.nextSibling);
36287
+ const caret = document.createRange();
36288
+ caret.setStart(after, 1);
36289
+ caret.collapse(true);
36290
+ sel.removeAllRanges();
36291
+ sel.addRange(caret);
36292
+ return true;
36293
+ }
36294
+ return false;
36295
+ }, []);
36296
+ const tryBlockMarkdown = useCallback(() => {
36297
+ const root = ref.current;
36298
+ if (!root) return false;
36299
+ const sel = window.getSelection();
36300
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
36301
+ const node = sel.anchorNode;
36302
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
36303
+ const block = closestBlock(node, root);
36304
+ if (block ? block.firstChild !== node : root.firstChild !== node) return false;
36305
+ const prefix = (node.textContent ?? "").slice(0, sel.anchorOffset);
36306
+ const rule = MD_BLOCK_PREFIXES[prefix];
36307
+ if (!rule) return false;
36308
+ if (node.parentElement?.closest("pre, code")) return false;
36309
+ if (rule.kind === "list" && node.parentElement?.closest("li")) return false;
36310
+ node.deleteData(0, prefix.length);
36311
+ const caret = document.createRange();
36312
+ caret.setStart(node, 0);
36313
+ caret.collapse(true);
36314
+ sel.removeAllRanges();
36315
+ sel.addRange(caret);
36316
+ if (rule.kind === "list") {
36317
+ const container = block ?? node.parentElement;
36318
+ const list = document.createElement(rule.command === "insertOrderedList" ? "ol" : "ul");
36319
+ const li = document.createElement("li");
36320
+ if (block) {
36321
+ while (block.firstChild) li.appendChild(block.firstChild);
36322
+ list.appendChild(li);
36323
+ block.replaceWith(list);
36324
+ } else if (container) {
36325
+ li.appendChild(node);
36326
+ list.appendChild(li);
36327
+ container.appendChild(list);
36328
+ }
36329
+ if (!li.firstChild) li.appendChild(document.createElement("br"));
36330
+ const liCaret = document.createRange();
36331
+ liCaret.selectNodeContents(li);
36332
+ liCaret.collapse(true);
36333
+ sel.removeAllRanges();
36334
+ sel.addRange(liCaret);
36335
+ } else {
36336
+ document.execCommand("formatBlock", false, rule.tag);
36337
+ }
36338
+ return true;
36339
+ }, []);
36340
+ const afterEdit = useCallback(() => {
36341
+ const el = ref.current;
36342
+ if (!el) return;
36343
+ tryInlineMarkdown();
36344
+ setEmpty(htmlIsEmpty(el));
36345
+ setToolbar(readToolbarState(el));
36346
+ scheduleEmit();
36347
+ }, [scheduleEmit, tryInlineMarkdown]);
36348
+ const handleKeyDown = useCallback((e) => {
36349
+ if (e.key === " " && tryBlockMarkdown()) {
36350
+ e.preventDefault();
36351
+ afterEdit();
36352
+ }
36353
+ }, [afterEdit, tryBlockMarkdown]);
36354
+ const execInline = useCallback((command) => {
36355
+ document.execCommand(command, false);
36356
+ afterEdit();
36357
+ }, [afterEdit]);
36358
+ const execBlock = useCallback((tag) => {
36359
+ const next = toolbar.block === tag ? "p" : tag;
36360
+ document.execCommand("formatBlock", false, next);
36361
+ afterEdit();
36362
+ }, [afterEdit, toolbar.block]);
36363
+ const execList = useCallback((command) => {
36364
+ document.execCommand(command, false);
36365
+ afterEdit();
36366
+ }, [afterEdit]);
36367
+ const execLink = useCallback(() => {
36368
+ if (toolbar.link) {
36369
+ document.execCommand("unlink", false);
36370
+ afterEdit();
36371
+ return;
36372
+ }
36373
+ const url = window.prompt(t("richTextEditor.linkPrompt"));
36374
+ if (!url) return;
36375
+ const safe = safeUrl(url, ["http://", "https://", "mailto:"]) ?? `https://${url}`;
36376
+ document.execCommand("createLink", false, safe);
36377
+ afterEdit();
36378
+ }, [afterEdit, t, toolbar.link]);
36379
+ const execRule = useCallback(() => {
36380
+ document.execCommand("insertHorizontalRule", false);
36381
+ afterEdit();
36382
+ }, [afterEdit]);
36383
+ if (readOnly) {
36384
+ return /* @__PURE__ */ jsxs(Box, { className: cn("almadar-rich-text max-w-none", className), children: [
36385
+ /* @__PURE__ */ jsx(RichTextStyles, {}),
36386
+ /* @__PURE__ */ jsx(Box, { dangerouslySetInnerHTML: { __html: sanitizeRichHtml(value ?? "") } })
36444
36387
  ] });
36445
36388
  }
36446
- case "bullet-list":
36447
- case "numbered-list": {
36448
- const items = block.children ?? [];
36449
- return /* @__PURE__ */ jsxs(
36389
+ return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
36390
+ /* @__PURE__ */ jsx(RichTextStyles, {}),
36391
+ showToolbar && /* @__PURE__ */ jsxs(
36450
36392
  Box,
36451
36393
  {
36452
- as: block.type === "bullet-list" ? "ul" : "ol",
36394
+ role: "toolbar",
36395
+ "aria-label": t("richTextEditor.editorToolbar"),
36453
36396
  className: cn(
36454
- "space-y-1 pl-6",
36455
- block.type === "bullet-list" ? "list-disc" : "list-decimal"
36397
+ "sticky top-0 z-10 flex flex-wrap items-center gap-0.5 self-start",
36398
+ "rounded-md border border-border bg-background/95 px-1 py-0.5 shadow-sm"
36456
36399
  ),
36457
36400
  children: [
36458
- items.map((child) => /* @__PURE__ */ jsxs(Box, { as: "li", className: "group/item flex items-start gap-2", children: [
36459
- /* @__PURE__ */ jsx(
36460
- Editable,
36461
- {
36462
- tag: "span",
36463
- value: child.content ?? "",
36464
- readOnly,
36465
- placeholder: t("richBlockEditor.placeholder.listItem"),
36466
- ariaLabel: t("richBlockEditor.aria.listItem"),
36467
- className: "inline-block min-w-[1ch] flex-1",
36468
- onValueChange: (next) => setChildContent(child.id, next)
36469
- }
36470
- ),
36471
- !readOnly && showAffordances && /* @__PURE__ */ jsx(
36472
- Button,
36473
- {
36474
- type: "button",
36475
- variant: "ghost",
36476
- "aria-label": t("richBlockEditor.aria.removeListItem"),
36477
- className: cn(
36478
- "h-5 w-5 shrink-0 rounded-sm text-muted-foreground p-0 gap-0",
36479
- "opacity-0 group-hover/item:opacity-100 hover:bg-muted hover:text-foreground"
36480
- ),
36481
- onClick: () => removeListItem(child.id),
36482
- children: /* @__PURE__ */ jsx(Icon, { name: "trash", className: "w-3 h-3" })
36483
- }
36484
- )
36485
- ] }, child.id)),
36486
- !readOnly && showAffordances && /* @__PURE__ */ jsx(Box, { as: "li", className: "list-none pl-0", children: /* @__PURE__ */ jsxs(
36487
- Button,
36488
- {
36489
- type: "button",
36490
- variant: "ghost",
36491
- className: cn(
36492
- "inline-flex items-center gap-1 text-xs text-muted-foreground",
36493
- "hover:text-foreground p-0 h-auto"
36494
- ),
36495
- onClick: addListItem,
36496
- children: [
36497
- /* @__PURE__ */ jsx(Icon, { name: "plus", className: "w-3 h-3" }),
36498
- " ",
36499
- t("richBlockEditor.addItem")
36500
- ]
36501
- }
36502
- ) })
36401
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Bold, label: t("richTextEditor.bold"), active: toolbar.bold, onExec: () => execInline("bold") }),
36402
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Italic, label: t("richTextEditor.italic"), active: toolbar.italic, onExec: () => execInline("italic") }),
36403
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Underline, label: t("richTextEditor.underline"), active: toolbar.underline, onExec: () => execInline("underline") }),
36404
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Strikethrough, label: t("richTextEditor.strikethrough"), active: toolbar.strikeThrough, onExec: () => execInline("strikeThrough") }),
36405
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
36406
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading1, label: t("richTextEditor.heading1"), active: toolbar.block === "h1", onExec: () => execBlock("h1") }),
36407
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading2, label: t("richTextEditor.heading2"), active: toolbar.block === "h2", onExec: () => execBlock("h2") }),
36408
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading3, label: t("richTextEditor.heading3"), active: toolbar.block === "h3", onExec: () => execBlock("h3") }),
36409
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
36410
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: List, label: t("richTextEditor.bulletList"), active: toolbar.bullets, onExec: () => execList("insertUnorderedList") }),
36411
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: ListOrdered, label: t("richTextEditor.numberedList"), active: toolbar.numbers, onExec: () => execList("insertOrderedList") }),
36412
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
36413
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
36414
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
36415
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Link$1, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
36416
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Minus, label: t("richTextEditor.divider"), onExec: execRule })
36503
36417
  ]
36504
36418
  }
36505
- );
36506
- }
36507
- case "paragraph":
36508
- default:
36509
- return /* @__PURE__ */ jsx(
36510
- Editable,
36419
+ ),
36420
+ /* @__PURE__ */ jsx(
36421
+ Box,
36511
36422
  {
36512
- tag: "p",
36513
- value: block.content ?? "",
36514
- readOnly,
36515
- placeholder: placeholder ?? t("richBlockEditor.placeholder.paragraph"),
36516
- ariaLabel: t("richBlockEditor.aria.paragraphBlock"),
36517
- className: "leading-7",
36518
- onValueChange: setContent
36423
+ ref,
36424
+ contentEditable: true,
36425
+ suppressContentEditableWarning: true,
36426
+ role: "textbox",
36427
+ "aria-multiline": "true",
36428
+ "aria-label": t("richTextEditor.editorSurface"),
36429
+ "data-placeholder": placeholder ?? t("richTextEditor.placeholder"),
36430
+ "data-empty": empty ? "true" : "false",
36431
+ className: cn(
36432
+ "almadar-rich-text max-w-none min-h-[8rem] outline-none",
36433
+ "data-[empty=true]:before:content-[attr(data-placeholder)]",
36434
+ "data-[empty=true]:before:text-muted-foreground/60",
36435
+ "data-[empty=true]:before:pointer-events-none",
36436
+ "data-[empty=true]:before:absolute",
36437
+ "relative cursor-text"
36438
+ ),
36439
+ onInput: afterEdit,
36440
+ onKeyDown: handleKeyDown,
36441
+ onBlur: flushEmit
36519
36442
  }
36520
- );
36521
- }
36443
+ )
36444
+ ] });
36445
+ };
36446
+ RichTextEditor.displayName = "RichTextEditor";
36447
+ }
36448
+ });
36449
+ function renderIconInput4(icon, props) {
36450
+ return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
36451
+ }
36452
+ function DocumentPanel({
36453
+ entity,
36454
+ title,
36455
+ subtitle,
36456
+ value,
36457
+ actions,
36458
+ maxInlineActions,
36459
+ editing = false,
36460
+ titleCommitEvent,
36461
+ contentChangeEvent,
36462
+ editEvent,
36463
+ doneEvent,
36464
+ editLabel,
36465
+ doneLabel,
36466
+ autosaveHint,
36467
+ placeholder,
36468
+ className
36469
+ }) {
36470
+ const eventBus = useEventBus();
36471
+ const { t } = useTranslate();
36472
+ const [titleDraft, setTitleDraft] = useState(null);
36473
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
36474
+ const actionDefs = actions ?? [];
36475
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
36476
+ const inlineActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
36477
+ const menuActions = actionDefs.filter((a) => !inlineActions.includes(a));
36478
+ const fireAction = (action) => {
36479
+ if (!action.event) return;
36480
+ eventBus.emit(`UI:${action.event}`, { id: recordId, row: entity });
36522
36481
  };
36523
- return /* @__PURE__ */ jsxs(
36482
+ const commitTitle = () => {
36483
+ if (!titleCommitEvent) return;
36484
+ const next = (titleDraft ?? "").trim();
36485
+ setTitleDraft(null);
36486
+ if (!next || next === title) return;
36487
+ eventBus.emit(`UI:${titleCommitEvent}`, { title: next, id: recordId });
36488
+ };
36489
+ const untitled = t("documentPanel.untitled") || "Untitled";
36490
+ const titleNode = titleCommitEvent && titleDraft !== null ? /* @__PURE__ */ jsx(
36491
+ Input,
36492
+ {
36493
+ value: titleDraft,
36494
+ autoFocus: true,
36495
+ "aria-label": t("common.title"),
36496
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
36497
+ onFocus: (e) => e.currentTarget.select(),
36498
+ onChange: (e) => setTitleDraft(e.target.value),
36499
+ onBlur: commitTitle,
36500
+ onKeyDown: (e) => {
36501
+ if (e.key === "Enter") {
36502
+ e.preventDefault();
36503
+ commitTitle();
36504
+ } else if (e.key === "Escape") {
36505
+ e.preventDefault();
36506
+ setTitleDraft(null);
36507
+ }
36508
+ },
36509
+ "data-testid": "document-title-input"
36510
+ }
36511
+ ) : titleCommitEvent ? /* @__PURE__ */ jsx(
36524
36512
  Box,
36525
36513
  {
36526
- className: cn(
36527
- "group relative flex items-start gap-2 rounded-sm",
36528
- "px-2 py-1 hover:bg-muted/30"
36529
- ),
36530
- "data-block-id": block.id,
36531
- "data-block-type": block.type,
36532
- children: [
36533
- !readOnly && showAffordances && /* @__PURE__ */ jsx(Box, { className: "flex w-8 shrink-0 items-center pt-1", children: /* @__PURE__ */ jsx(
36534
- BlockMenu,
36514
+ role: "button",
36515
+ tabIndex: 0,
36516
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
36517
+ onClick: () => setTitleDraft(title ?? ""),
36518
+ onKeyDown: (e) => {
36519
+ if (e.key === "Enter" || e.key === " ") {
36520
+ e.preventDefault();
36521
+ setTitleDraft(title ?? "");
36522
+ }
36523
+ },
36524
+ "data-testid": "document-title-editable",
36525
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", className: cn(!title && "text-muted-foreground"), children: title || untitled })
36526
+ }
36527
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || untitled });
36528
+ const emitWithId = (event) => () => {
36529
+ if (event) eventBus.emit(`UI:${event}`, { id: recordId });
36530
+ };
36531
+ return /* @__PURE__ */ jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
36532
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
36533
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
36534
+ titleNode,
36535
+ subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
36536
+ ] }),
36537
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
36538
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
36539
+ /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
36540
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
36541
+ editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
36542
+ /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
36543
+ editLabel ?? (t("documentPanel.edit") || "Edit")
36544
+ ] }),
36545
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
36546
+ Button,
36535
36547
  {
36536
- block,
36537
- readOnly,
36538
- onDelete,
36539
- onDuplicate,
36540
- onChangeType
36548
+ variant: "primary",
36549
+ size: "sm",
36550
+ onClick: () => fireAction(action),
36551
+ "data-testid": `action-${action.event}`,
36552
+ children: [
36553
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
36554
+ action.label
36555
+ ]
36556
+ },
36557
+ idx
36558
+ )),
36559
+ menuActions.length > 0 && /* @__PURE__ */ jsx(
36560
+ Menu,
36561
+ {
36562
+ position: "bottom-end",
36563
+ 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" }) }),
36564
+ items: menuActions.map((action) => ({
36565
+ label: action.label,
36566
+ icon: action.icon,
36567
+ variant: action.variant === "danger" ? "danger" : "default",
36568
+ onClick: () => fireAction(action)
36569
+ }))
36541
36570
  }
36542
- ) }),
36543
- /* @__PURE__ */ jsx(Box, { className: "min-w-0 flex-1", children: renderBody() })
36544
- ]
36545
- }
36546
- );
36571
+ )
36572
+ ] }) })
36573
+ ] }),
36574
+ editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
36575
+ Box,
36576
+ {
36577
+ onClick: emitWithId(editEvent),
36578
+ className: cn(
36579
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
36580
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
36581
+ ),
36582
+ "data-testid": "document-body",
36583
+ children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
36584
+ }
36585
+ )
36586
+ ] }) });
36547
36587
  }
36548
- var TOOLBAR_ENTRIES, BLOCK_TYPE_LABEL_KEY, CHANGEABLE_TYPES, _idSeq, BLOCK_TYPES, RichBlockEditor;
36549
- var init_RichBlockEditor = __esm({
36550
- "components/core/molecules/RichBlockEditor.tsx"() {
36588
+ var init_DocumentPanel = __esm({
36589
+ "components/core/molecules/DocumentPanel.tsx"() {
36551
36590
  "use client";
36552
36591
  init_cn();
36553
- init_Card();
36592
+ init_useEventBus();
36593
+ init_Box();
36594
+ init_Stack();
36554
36595
  init_Typography();
36555
36596
  init_Button();
36556
- init_Box();
36557
- init_Divider();
36558
- init_Input();
36597
+ init_Card();
36559
36598
  init_Icon();
36560
- init_useEventBus();
36561
- TOOLBAR_ENTRIES = [
36562
- { type: "paragraph", labelKey: "richBlockEditor.toolbar.text", icon: Type },
36563
- { type: "heading-1", labelKey: "richBlockEditor.toolbar.h1", icon: Heading1 },
36564
- { type: "heading-2", labelKey: "richBlockEditor.toolbar.h2", icon: Heading2 },
36565
- { type: "heading-3", labelKey: "richBlockEditor.toolbar.h3", icon: Heading3 },
36566
- { type: "bullet-list", labelKey: "richBlockEditor.toolbar.bulletList", icon: List },
36567
- { type: "numbered-list", labelKey: "richBlockEditor.toolbar.numbered", icon: ListOrdered },
36568
- { type: "quote", labelKey: "richBlockEditor.toolbar.quote", icon: Quote },
36569
- { type: "code", labelKey: "richBlockEditor.toolbar.code", icon: Code },
36570
- { type: "divider", labelKey: "richBlockEditor.toolbar.divider", icon: Minus },
36571
- { type: "image", labelKey: "richBlockEditor.toolbar.image", icon: Image$1 }
36572
- ];
36573
- BLOCK_TYPE_LABEL_KEY = {
36574
- paragraph: "richBlockEditor.blockType.paragraph",
36575
- "heading-1": "richBlockEditor.blockType.heading1",
36576
- "heading-2": "richBlockEditor.blockType.heading2",
36577
- "heading-3": "richBlockEditor.blockType.heading3",
36578
- "bullet-list": "richBlockEditor.blockType.bulletList",
36579
- "numbered-list": "richBlockEditor.blockType.numberedList",
36580
- quote: "richBlockEditor.blockType.quote",
36581
- code: "richBlockEditor.blockType.code",
36582
- divider: "richBlockEditor.blockType.divider",
36583
- image: "richBlockEditor.blockType.image"
36584
- };
36585
- CHANGEABLE_TYPES = [
36586
- "paragraph",
36587
- "heading-1",
36588
- "heading-2",
36589
- "heading-3",
36590
- "bullet-list",
36591
- "numbered-list",
36592
- "quote",
36593
- "code"
36594
- ];
36595
- _idSeq = 0;
36596
- BLOCK_TYPES = /* @__PURE__ */ new Set([
36597
- "paragraph",
36598
- "heading-1",
36599
- "heading-2",
36600
- "heading-3",
36601
- "bullet-list",
36602
- "numbered-list",
36603
- "quote",
36604
- "code",
36605
- "divider",
36606
- "image"
36607
- ]);
36608
- RichBlockEditor = ({
36609
- initialBlocks,
36610
- onChange,
36611
- changeEvent,
36612
- readOnly = false,
36613
- placeholder,
36614
- enableBlocks = true,
36615
- showToolbar = true,
36616
- className
36617
- }) => {
36618
- const { t } = useTranslate();
36619
- const [blocks, setBlocks] = useState(
36620
- () => normalizeBlocks(initialBlocks)
36621
- );
36622
- const onChangeRef = useRef(onChange);
36623
- useEffect(() => {
36624
- onChangeRef.current = onChange;
36625
- }, [onChange]);
36626
- const eventBus = useEventBus();
36627
- const changeEventRef = useRef(changeEvent);
36628
- useEffect(() => {
36629
- changeEventRef.current = changeEvent;
36630
- }, [changeEvent]);
36631
- const commit = useCallback((next) => {
36632
- setBlocks(next);
36633
- onChangeRef.current?.(next);
36634
- const evt = changeEventRef.current;
36635
- if (evt) eventBus.emit(`UI:${evt}`, { blocks: next });
36636
- }, [eventBus]);
36637
- const handleAppend = useCallback(
36638
- (type) => {
36639
- if (readOnly) return;
36640
- commit([...blocks, createBlock(type)]);
36641
- },
36642
- [blocks, commit, readOnly]
36643
- );
36644
- const handleUpdate = useCallback(
36645
- (id, updater) => {
36646
- commit(replaceBlock(blocks, id, updater));
36647
- },
36648
- [blocks, commit]
36649
- );
36650
- const handleDelete = useCallback(
36651
- (id) => {
36652
- const next = removeBlock(blocks, id);
36653
- commit(next.length > 0 ? next : [createBlock("paragraph")]);
36654
- },
36655
- [blocks, commit]
36656
- );
36657
- const handleDuplicate = useCallback(
36658
- (id) => {
36659
- const target = blocks.find((b) => b.id === id);
36660
- if (!target) return;
36661
- commit(insertAfter(blocks, id, duplicateBlock(target)));
36662
- },
36663
- [blocks, commit]
36599
+ init_Input();
36600
+ init_Menu();
36601
+ init_RichTextEditor();
36602
+ }
36603
+ });
36604
+ function renderIconInput5(icon, props) {
36605
+ return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
36606
+ }
36607
+ function fieldName(field) {
36608
+ return field.name ?? field.key ?? "";
36609
+ }
36610
+ function relationLabel(value) {
36611
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
36612
+ for (const key of ["name", "title", "label"]) {
36613
+ const candidate = value[key];
36614
+ if (typeof candidate === "string" && candidate !== "") return candidate;
36615
+ }
36616
+ const id = value.id;
36617
+ return id !== void 0 && id !== null ? String(id) : null;
36618
+ }
36619
+ function fieldKind(field, value) {
36620
+ if (field.kind) return field.kind;
36621
+ if (field.options && field.options.length > 0) return "select";
36622
+ if (typeof value === "boolean" || field.format === "boolean") return "boolean";
36623
+ if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
36624
+ return "text";
36625
+ }
36626
+ function DocumentDetails({
36627
+ entity,
36628
+ fields,
36629
+ metaCommitEvent,
36630
+ title,
36631
+ className
36632
+ }) {
36633
+ const eventBus = useEventBus();
36634
+ const { t } = useTranslate();
36635
+ const [fieldDraft, setFieldDraft] = useState(null);
36636
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
36637
+ const fieldDefs = (fields ?? []).filter((f3) => fieldName(f3) !== "");
36638
+ if (fieldDefs.length === 0) return null;
36639
+ const commitField = (name, next, current) => {
36640
+ setFieldDraft(null);
36641
+ if (!metaCommitEvent || next === current) return;
36642
+ eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
36643
+ };
36644
+ const renderValue = (field) => {
36645
+ const name = fieldName(field);
36646
+ const raw = getNestedValue(entity ?? {}, name);
36647
+ const kind = fieldKind(field, raw);
36648
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
36649
+ if (kind === "boolean") {
36650
+ return /* @__PURE__ */ jsx(
36651
+ Switch,
36652
+ {
36653
+ checked: Boolean(raw),
36654
+ disabled: !metaCommitEvent,
36655
+ "aria-label": label,
36656
+ onChange: (checked) => commitField(name, checked, Boolean(raw))
36657
+ }
36664
36658
  );
36665
- const handleChangeType = useCallback(
36666
- (id, type) => {
36667
- commit(
36668
- replaceBlock(blocks, id, (b) => changeBlockType(b, type))
36669
- );
36670
- },
36671
- [blocks, commit]
36659
+ }
36660
+ if (kind === "select") {
36661
+ return /* @__PURE__ */ jsx(
36662
+ Select,
36663
+ {
36664
+ value: raw !== void 0 && raw !== null ? String(raw) : "",
36665
+ disabled: !metaCommitEvent,
36666
+ "aria-label": label,
36667
+ className: "h-8 w-full",
36668
+ options: (field.options ?? []).map((opt) => ({ value: opt, label: humanizeEnumValue(opt) })),
36669
+ onChange: (e) => commitField(name, e.target.value, raw)
36670
+ }
36672
36671
  );
36673
- return /* @__PURE__ */ jsxs(
36674
- Card,
36672
+ }
36673
+ if (kind === "readonly" || !metaCommitEvent) {
36674
+ if (Array.isArray(raw)) {
36675
+ if (raw.length === 0) {
36676
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
36677
+ }
36678
+ 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)) });
36679
+ }
36680
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
36681
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
36682
+ }
36683
+ if (fieldDraft?.name === name) {
36684
+ return /* @__PURE__ */ jsx(
36685
+ Input,
36675
36686
  {
36676
- variant: "bordered",
36677
- padding: "none",
36678
- className: cn("flex flex-col text-card-foreground", className),
36679
- children: [
36680
- enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsx(
36681
- Box,
36682
- {
36683
- role: "toolbar",
36684
- "aria-label": t("richBlockEditor.editorToolbar"),
36685
- className: cn(
36686
- "flex flex-wrap items-center gap-1",
36687
- "border-b border-border bg-muted/30 px-2 py-2"
36688
- ),
36689
- children: TOOLBAR_ENTRIES.map((entry) => {
36690
- const Icon2 = entry.icon;
36691
- const entryLabel = t(entry.labelKey);
36692
- return /* @__PURE__ */ jsxs(
36693
- Button,
36694
- {
36695
- type: "button",
36696
- variant: "ghost",
36697
- size: "sm",
36698
- "aria-label": t("richBlockEditor.insertEntry", { label: entryLabel }),
36699
- title: entryLabel,
36700
- onClick: () => handleAppend(entry.type),
36701
- children: [
36702
- /* @__PURE__ */ jsx(Icon2, { size: 14 }),
36703
- /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "ml-1 hidden text-xs sm:inline", children: entryLabel })
36704
- ]
36705
- },
36706
- entry.type
36707
- );
36708
- })
36709
- }
36710
- ),
36711
- /* @__PURE__ */ jsx(Box, { className: "flex flex-col gap-1 px-3 py-3", children: blocks.map((block) => /* @__PURE__ */ jsx(
36712
- BlockRow,
36713
- {
36714
- block,
36715
- readOnly,
36716
- showAffordances: enableBlocks,
36717
- placeholder,
36718
- onUpdate: (updater) => handleUpdate(block.id, updater),
36719
- onDelete: () => handleDelete(block.id),
36720
- onDuplicate: () => handleDuplicate(block.id),
36721
- onChangeType: (type) => handleChangeType(block.id, type)
36722
- },
36723
- block.id
36724
- )) })
36725
- ]
36687
+ value: fieldDraft.value,
36688
+ autoFocus: true,
36689
+ "aria-label": label,
36690
+ className: "h-8 w-full",
36691
+ onChange: (e) => setFieldDraft({ name, value: e.target.value }),
36692
+ onBlur: () => commitField(name, fieldDraft.value.trim(), raw),
36693
+ onKeyDown: (e) => {
36694
+ if (e.key === "Enter") {
36695
+ e.preventDefault();
36696
+ commitField(name, fieldDraft.value.trim(), raw);
36697
+ } else if (e.key === "Escape") {
36698
+ e.preventDefault();
36699
+ setFieldDraft(null);
36700
+ }
36701
+ },
36702
+ "data-testid": `document-property-input-${name}`
36726
36703
  }
36727
36704
  );
36728
- };
36729
- RichBlockEditor.displayName = "RichBlockEditor";
36705
+ }
36706
+ const shown = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
36707
+ return /* @__PURE__ */ jsx(
36708
+ Box,
36709
+ {
36710
+ role: "button",
36711
+ tabIndex: 0,
36712
+ className: cn(
36713
+ "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
36714
+ (raw === void 0 || raw === null || raw === "") && "text-muted-foreground"
36715
+ ),
36716
+ onClick: () => setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" }),
36717
+ onKeyDown: (e) => {
36718
+ if (e.key === "Enter" || e.key === " ") {
36719
+ e.preventDefault();
36720
+ setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" });
36721
+ }
36722
+ },
36723
+ "data-testid": `document-property-${name}`,
36724
+ children: /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown })
36725
+ }
36726
+ );
36727
+ };
36728
+ return /* @__PURE__ */ jsx(Card, { variant: "bordered", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-4", children: [
36729
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", weight: "medium", className: "uppercase tracking-wide", children: title ?? (t("documentDetails.title") || "Details") }),
36730
+ /* @__PURE__ */ jsx(VStack, { gap: "sm", children: fieldDefs.map((field) => {
36731
+ const name = fieldName(field);
36732
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
36733
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
36734
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
36735
+ field.icon && renderIconInput5(field.icon, { size: "xs", className: "text-muted-foreground" }),
36736
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: label })
36737
+ ] }),
36738
+ /* @__PURE__ */ jsx(Box, { className: "min-w-0", children: renderValue(field) })
36739
+ ] }, name);
36740
+ }) })
36741
+ ] }) });
36742
+ }
36743
+ var init_DocumentDetails = __esm({
36744
+ "components/core/molecules/DocumentDetails.tsx"() {
36745
+ "use client";
36746
+ init_cn();
36747
+ init_format();
36748
+ init_getNestedValue();
36749
+ init_useEventBus();
36750
+ init_Box();
36751
+ init_Stack();
36752
+ init_Typography();
36753
+ init_Badge();
36754
+ init_Card();
36755
+ init_Icon();
36756
+ init_Input();
36757
+ init_Select();
36758
+ init_Switch();
36730
36759
  }
36731
36760
  });
36732
36761
  function collectInitiallyCollapsed(nodes, acc) {
@@ -41675,8 +41704,8 @@ var init_DataTable = __esm({
41675
41704
  DataTable.displayName = "DataTable";
41676
41705
  }
41677
41706
  });
41678
- function getBadgeVariant(fieldName, value) {
41679
- const name = fieldName.toLowerCase();
41707
+ function getBadgeVariant(fieldName2, value) {
41708
+ const name = fieldName2.toLowerCase();
41680
41709
  const val = String(value).toLowerCase();
41681
41710
  if (name.includes("status")) {
41682
41711
  if (val.includes("complete") || val.includes("done") || val.includes("active"))
@@ -41692,12 +41721,12 @@ function getBadgeVariant(fieldName, value) {
41692
41721
  }
41693
41722
  return "default";
41694
41723
  }
41695
- function formatFieldValue2(value, fieldName) {
41724
+ function formatFieldValue2(value, fieldName2) {
41696
41725
  if (typeof value === "number") {
41697
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
41726
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
41698
41727
  return `${value}%`;
41699
41728
  }
41700
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
41729
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
41701
41730
  return `$${value.toLocaleString()}`;
41702
41731
  }
41703
41732
  return value.toLocaleString();
@@ -41707,7 +41736,7 @@ function formatFieldValue2(value, fieldName) {
41707
41736
  }
41708
41737
  return String(value);
41709
41738
  }
41710
- function renderRichFieldValue(value, fieldName, fieldType, meta) {
41739
+ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
41711
41740
  if (value === void 0 || value === null) return "\u2014";
41712
41741
  const str = String(value);
41713
41742
  switch (fieldType) {
@@ -41718,7 +41747,7 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
41718
41747
  "img",
41719
41748
  {
41720
41749
  src: str,
41721
- alt: formatFieldLabel(fieldName),
41750
+ alt: formatFieldLabel(fieldName2),
41722
41751
  className: "max-w-full max-h-64 rounded-md object-contain",
41723
41752
  loading: "lazy"
41724
41753
  }
@@ -41853,9 +41882,9 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
41853
41882
  return /* @__PURE__ */ jsx("a", { href: `tel:${str}`, className: "text-primary hover:underline", children: str });
41854
41883
  default:
41855
41884
  if (meta?.values && meta.values.length > 0 && meta.values.includes(str)) {
41856
- return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName, str), children: humanizeEnumValue(str) });
41885
+ return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName2, str), children: humanizeEnumValue(str) });
41857
41886
  }
41858
- return formatFieldValue2(value, fieldName);
41887
+ return formatFieldValue2(value, fieldName2);
41859
41888
  }
41860
41889
  }
41861
41890
  function normalizeFieldDefs(fields) {
@@ -42325,16 +42354,11 @@ var init_DetailPanel = __esm({
42325
42354
  footer
42326
42355
  ] })
42327
42356
  ] }) });
42328
- return /* @__PURE__ */ jsx(
42329
- Box,
42330
- {
42331
- className: cn(
42332
- slideOver && "fixed inset-y-0 right-0 w-full max-w-2xl bg-card shadow-lg z-50 overflow-y-auto p-6",
42333
- className
42334
- ),
42335
- children: content
42336
- }
42337
- );
42357
+ if (!slideOver) {
42358
+ return /* @__PURE__ */ jsx(Box, { className, children: content });
42359
+ }
42360
+ 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 });
42361
+ return typeof document === "undefined" ? panel : createPortal(panel, document.body);
42338
42362
  };
42339
42363
  DetailPanel.displayName = "DetailPanel";
42340
42364
  }
@@ -42703,7 +42727,21 @@ var init_Form = __esm({
42703
42727
  const normalizedInitialData = React85__default.useMemo(() => {
42704
42728
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
42705
42729
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
42706
- return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
42730
+ const merged = entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
42731
+ const toId = (value) => {
42732
+ if (value === null || value === void 0 || typeof value !== "object" || value instanceof Date) return value;
42733
+ if (Array.isArray(value)) {
42734
+ return value.map(
42735
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && item.id !== void 0 && item.id !== null ? String(item.id) : item
42736
+ );
42737
+ }
42738
+ return value.id !== void 0 && value.id !== null ? String(value.id) : value;
42739
+ };
42740
+ const normalized = {};
42741
+ for (const [key, value] of Object.entries(merged)) {
42742
+ normalized[key] = key === "id" ? value : toId(value);
42743
+ }
42744
+ return normalized;
42707
42745
  }, [entity, initialData]);
42708
42746
  const entityDerivedFields = React85__default.useMemo(() => {
42709
42747
  if (fields && fields.length > 0) return void 0;
@@ -42832,8 +42870,8 @@ var init_Form = __esm({
42832
42870
  checkViolations(name, newFormData);
42833
42871
  };
42834
42872
  const isFieldVisible = React85__default.useCallback(
42835
- (fieldName) => {
42836
- const condition = conditionalFields[fieldName];
42873
+ (fieldName2) => {
42874
+ const condition = conditionalFields[fieldName2];
42837
42875
  if (!condition) return true;
42838
42876
  return Boolean(evaluateFormExpression(condition, evalContext));
42839
42877
  },
@@ -42879,9 +42917,9 @@ var init_Form = __esm({
42879
42917
  };
42880
42918
  const handleInvalid = (e) => {
42881
42919
  const target = e.target;
42882
- const fieldName = target.getAttribute("data-field-name") ?? target.name ?? "";
42920
+ const fieldName2 = target.getAttribute("data-field-name") ?? target.name ?? "";
42883
42921
  const fieldMessage = target.validationMessage || "Invalid value";
42884
- debug("forms", "invalid", { mode: formMode, fieldName, fieldMessage });
42922
+ debug("forms", "invalid", { mode: formMode, fieldName: fieldName2, fieldMessage });
42885
42923
  queueMicrotask(() => {
42886
42924
  const form = formRef.current;
42887
42925
  if (!form) return;
@@ -42917,22 +42955,22 @@ var init_Form = __esm({
42917
42955
  };
42918
42956
  const renderField = React85__default.useCallback(
42919
42957
  (field) => {
42920
- const fieldName = field.name || field.field;
42921
- if (!fieldName) return null;
42922
- if (!isFieldVisible(fieldName)) {
42958
+ const fieldName2 = field.name || field.field;
42959
+ if (!fieldName2) return null;
42960
+ if (!isFieldVisible(fieldName2)) {
42923
42961
  return null;
42924
42962
  }
42925
42963
  const inputType = determineInputType(field);
42926
- const label = field.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, " $1");
42927
- const currentValue = formData[fieldName] ?? field.defaultValue ?? "";
42928
- return /* @__PURE__ */ jsxs(VStack, { gap: "xs", "data-field": fieldName, children: [
42964
+ const label = field.label || fieldName2.charAt(0).toUpperCase() + fieldName2.slice(1).replace(/([A-Z])/g, " $1");
42965
+ const currentValue = formData[fieldName2] ?? field.defaultValue ?? "";
42966
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", "data-field": fieldName2, children: [
42929
42967
  inputType !== "checkbox" && /* @__PURE__ */ jsxs(Typography, { as: "label", variant: "label", weight: "bold", children: [
42930
42968
  label,
42931
42969
  field.required && /* @__PURE__ */ jsx(Typography, { as: "span", color: "error", className: "ml-1", children: "*" })
42932
42970
  ] }),
42933
- renderFieldInput(field, fieldName, inputType, currentValue, label),
42971
+ renderFieldInput(field, fieldName2, inputType, currentValue, label),
42934
42972
  field.hint && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: field.hint })
42935
- ] }, fieldName);
42973
+ ] }, fieldName2);
42936
42974
  },
42937
42975
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
42938
42976
  );
@@ -42961,8 +42999,8 @@ var init_Form = __esm({
42961
42999
  }
42962
43000
  return field;
42963
43001
  }).map((field) => {
42964
- const fieldName = field.name || field.field;
42965
- const override = fieldOverrides?.find((o) => o.name === fieldName);
43002
+ const fieldName2 = field.name || field.field;
43003
+ const override = fieldOverrides?.find((o) => o.name === fieldName2);
42966
43004
  if (!override) return field;
42967
43005
  return {
42968
43006
  ...field,
@@ -43018,11 +43056,11 @@ var init_Form = __esm({
43018
43056
  ] }, section.id);
43019
43057
  }).filter(Boolean);
43020
43058
  }, [sections, isSectionVisible, collapsedSections, renderField, gap]);
43021
- function renderFieldInput(field, fieldName, inputType, currentValue, label) {
43059
+ function renderFieldInput(field, fieldName2, inputType, currentValue, label) {
43022
43060
  const commonProps = {
43023
- id: fieldName,
43024
- name: fieldName,
43025
- "data-field-name": fieldName,
43061
+ id: fieldName2,
43062
+ name: fieldName2,
43063
+ "data-field-name": fieldName2,
43026
43064
  required: field.required,
43027
43065
  disabled: isLoading,
43028
43066
  placeholder: field.placeholder,
@@ -43036,7 +43074,7 @@ var init_Form = __esm({
43036
43074
  ...commonProps,
43037
43075
  label: label + (field.required ? " *" : ""),
43038
43076
  checked: Boolean(currentValue),
43039
- onChange: (e) => handleChange(fieldName, e.target.checked)
43077
+ onChange: (e) => handleChange(fieldName2, e.target.checked)
43040
43078
  }
43041
43079
  );
43042
43080
  case "textarea":
@@ -43045,7 +43083,7 @@ var init_Form = __esm({
43045
43083
  {
43046
43084
  ...commonProps,
43047
43085
  value: String(currentValue),
43048
- onChange: (e) => handleChange(fieldName, e.target.value),
43086
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43049
43087
  minLength: field.min,
43050
43088
  maxLength: field.max
43051
43089
  }
@@ -43058,14 +43096,14 @@ var init_Form = __esm({
43058
43096
  ...commonProps,
43059
43097
  options,
43060
43098
  value: String(currentValue),
43061
- onValueChange: (v) => handleChange(fieldName, v),
43099
+ onValueChange: (v) => handleChange(fieldName2, v),
43062
43100
  placeholder: field.placeholder || `Select ${label}...`
43063
43101
  }
43064
43102
  );
43065
43103
  }
43066
43104
  case "relation": {
43067
- const relationOptions = relationsData[fieldName] || [];
43068
- const relationLoading = relationsLoading[fieldName] || false;
43105
+ const relationOptions = relationsData[fieldName2] || [];
43106
+ const relationLoading = relationsLoading[fieldName2] || false;
43069
43107
  if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
43070
43108
  const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
43071
43109
  return /* @__PURE__ */ jsx(
@@ -43077,7 +43115,7 @@ var init_Form = __esm({
43077
43115
  clearable: true,
43078
43116
  options: [...relationOptions],
43079
43117
  value: selectedValues,
43080
- onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
43118
+ onValueChange: (value) => handleChange(fieldName2, Array.isArray(value) ? value : [value]),
43081
43119
  placeholder: field.placeholder || `Select ${label}...`
43082
43120
  }
43083
43121
  );
@@ -43087,7 +43125,7 @@ var init_Form = __esm({
43087
43125
  {
43088
43126
  ...commonProps,
43089
43127
  value: currentValue ? String(currentValue) : void 0,
43090
- onChange: (value) => handleChange(fieldName, value),
43128
+ onChange: (value) => handleChange(fieldName2, value),
43091
43129
  options: relationOptions,
43092
43130
  isLoading: relationLoading,
43093
43131
  placeholder: field.placeholder || `Select ${label}...`,
@@ -43104,7 +43142,7 @@ var init_Form = __esm({
43104
43142
  placeholder: field.placeholder,
43105
43143
  disabled: isLoading,
43106
43144
  value: arrayValue,
43107
- onChange: (next) => handleChange(fieldName, [...next])
43145
+ onChange: (next) => handleChange(fieldName2, [...next])
43108
43146
  }
43109
43147
  );
43110
43148
  }
@@ -43116,7 +43154,7 @@ var init_Form = __esm({
43116
43154
  type: "number",
43117
43155
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
43118
43156
  onChange: (e) => handleChange(
43119
- fieldName,
43157
+ fieldName2,
43120
43158
  e.target.value ? Number(e.target.value) : void 0
43121
43159
  ),
43122
43160
  min: field.min,
@@ -43133,7 +43171,7 @@ var init_Form = __esm({
43133
43171
  icon: DollarSign,
43134
43172
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
43135
43173
  onChange: (e) => handleChange(
43136
- fieldName,
43174
+ fieldName2,
43137
43175
  e.target.value ? Number(e.target.value) : void 0
43138
43176
  ),
43139
43177
  min: field.min,
@@ -43151,7 +43189,7 @@ var init_Form = __esm({
43151
43189
  const f3 = files[0];
43152
43190
  if (!f3) return;
43153
43191
  const reader = new FileReader();
43154
- reader.onload = () => handleChange(fieldName, {
43192
+ reader.onload = () => handleChange(fieldName2, {
43155
43193
  name: f3.name,
43156
43194
  mimeType: f3.type,
43157
43195
  sizeBytes: f3.size,
@@ -43168,7 +43206,7 @@ var init_Form = __esm({
43168
43206
  ...commonProps,
43169
43207
  type: "date",
43170
43208
  value: formatDateValue(currentValue),
43171
- onChange: (e) => handleChange(fieldName, e.target.value)
43209
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43172
43210
  }
43173
43211
  );
43174
43212
  case "datetime-local":
@@ -43178,7 +43216,7 @@ var init_Form = __esm({
43178
43216
  ...commonProps,
43179
43217
  type: "datetime-local",
43180
43218
  value: formatDateTimeValue(currentValue),
43181
- onChange: (e) => handleChange(fieldName, e.target.value)
43219
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43182
43220
  }
43183
43221
  );
43184
43222
  case "email":
@@ -43188,7 +43226,7 @@ var init_Form = __esm({
43188
43226
  ...commonProps,
43189
43227
  type: "email",
43190
43228
  value: String(currentValue),
43191
- onChange: (e) => handleChange(fieldName, e.target.value),
43229
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43192
43230
  minLength: field.min,
43193
43231
  maxLength: field.max
43194
43232
  }
@@ -43200,7 +43238,7 @@ var init_Form = __esm({
43200
43238
  ...commonProps,
43201
43239
  type: "url",
43202
43240
  value: String(currentValue),
43203
- onChange: (e) => handleChange(fieldName, e.target.value),
43241
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43204
43242
  minLength: field.min,
43205
43243
  maxLength: field.max
43206
43244
  }
@@ -43212,7 +43250,7 @@ var init_Form = __esm({
43212
43250
  ...commonProps,
43213
43251
  type: "password",
43214
43252
  value: String(currentValue),
43215
- onChange: (e) => handleChange(fieldName, e.target.value),
43253
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43216
43254
  minLength: field.min,
43217
43255
  maxLength: field.max
43218
43256
  }
@@ -43225,7 +43263,7 @@ var init_Form = __esm({
43225
43263
  ...commonProps,
43226
43264
  type: "text",
43227
43265
  value: String(currentValue),
43228
- onChange: (e) => handleChange(fieldName, e.target.value),
43266
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43229
43267
  minLength: field.min,
43230
43268
  maxLength: field.max
43231
43269
  }
@@ -43582,7 +43620,7 @@ function entityFieldsFromListItem(item) {
43582
43620
  }
43583
43621
  return result;
43584
43622
  }
43585
- function getStatusStyle(fieldName, value) {
43623
+ function getStatusStyle(fieldName2, value) {
43586
43624
  const val = String(value).toLowerCase();
43587
43625
  if (val.includes("complete") || val.includes("done"))
43588
43626
  return STATUS_STYLES.complete;
@@ -43598,12 +43636,12 @@ function getStatusStyle(fieldName, value) {
43598
43636
  if (val.includes("low")) return STATUS_STYLES.low;
43599
43637
  return STATUS_STYLES.default;
43600
43638
  }
43601
- function formatValue3(value, fieldName) {
43639
+ function formatValue3(value, fieldName2) {
43602
43640
  if (typeof value === "number") {
43603
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
43641
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
43604
43642
  return `${value}%`;
43605
43643
  }
43606
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
43644
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
43607
43645
  return new Intl.NumberFormat("en-US", {
43608
43646
  style: "currency",
43609
43647
  currency: "USD",
@@ -43626,8 +43664,8 @@ function formatValue3(value, fieldName) {
43626
43664
  }
43627
43665
  return String(value);
43628
43666
  }
43629
- function formatFieldLabel2(fieldName) {
43630
- return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
43667
+ function formatFieldLabel2(fieldName2) {
43668
+ return humanizeFieldName(fieldName2).replace(/\sId$/, "").trim();
43631
43669
  }
43632
43670
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
43633
43671
  var init_List = __esm({
@@ -43695,9 +43733,9 @@ var init_List = __esm({
43695
43733
  };
43696
43734
  StatusBadge = ({
43697
43735
  value,
43698
- fieldName
43736
+ fieldName: fieldName2
43699
43737
  }) => {
43700
- const style = getStatusStyle(fieldName, value);
43738
+ const style = getStatusStyle(fieldName2, value);
43701
43739
  return /* @__PURE__ */ jsxs(
43702
43740
  Typography,
43703
43741
  {
@@ -46629,8 +46667,8 @@ var init_StatCard = __esm({
46629
46667
  return items.length;
46630
46668
  }
46631
46669
  if (field.includes(":")) {
46632
- const [fieldName, fieldValue] = field.split(":");
46633
- return items.filter((item) => item[fieldName] === fieldValue).length;
46670
+ const [fieldName2, fieldValue] = field.split(":");
46671
+ return items.filter((item) => item[fieldName2] === fieldValue).length;
46634
46672
  }
46635
46673
  const fieldExistsOnItems = items.some((item) => field in item);
46636
46674
  if (fieldExistsOnItems) {
@@ -48150,6 +48188,8 @@ var init_component_registry_generated = __esm({
48150
48188
  init_DocSearch();
48151
48189
  init_DocSidebar();
48152
48190
  init_DocTOC();
48191
+ init_DocumentDetails();
48192
+ init_DocumentPanel();
48153
48193
  init_DocumentViewer();
48154
48194
  init_DrawFxLayer();
48155
48195
  init_DrawGroup();
@@ -48259,7 +48299,7 @@ var init_component_registry_generated = __esm({
48259
48299
  init_RelationSelect();
48260
48300
  init_RepeatableFormSection();
48261
48301
  init_ReplyTree();
48262
- init_RichBlockEditor();
48302
+ init_RichTextEditor();
48263
48303
  init_RuntimeDebugger2();
48264
48304
  init_ScaledDiagram();
48265
48305
  init_ScoreDisplay();
@@ -48422,6 +48462,8 @@ var init_component_registry_generated = __esm({
48422
48462
  "DocSearch": DocSearch,
48423
48463
  "DocSidebar": DocSidebar,
48424
48464
  "DocTOC": DocTOC,
48465
+ "DocumentDetails": DocumentDetails,
48466
+ "DocumentPanel": DocumentPanel,
48425
48467
  "DocumentViewer": DocumentViewer,
48426
48468
  "DrawFxLayer": DrawFxLayer,
48427
48469
  "DrawGroup": DrawGroup,
@@ -48533,7 +48575,7 @@ var init_component_registry_generated = __esm({
48533
48575
  "RelationSelect": RelationSelect,
48534
48576
  "RepeatableFormSection": RepeatableFormSection,
48535
48577
  "ReplyTree": ReplyTree,
48536
- "RichBlockEditor": RichBlockEditor,
48578
+ "RichTextEditor": RichTextEditor,
48537
48579
  "RuntimeDebugger": RuntimeDebugger,
48538
48580
  "ScaledDiagram": ScaledDiagram,
48539
48581
  "ScoreDisplay": ScoreDisplay,
@@ -48687,10 +48729,10 @@ function enrichFormFields(fields, entityDef) {
48687
48729
  }
48688
48730
  if (field && typeof field === "object" && !Array.isArray(field) && !React85__default.isValidElement(field) && !(field instanceof Date)) {
48689
48731
  const obj = field;
48690
- const fieldName = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
48691
- if (!fieldName) return field;
48732
+ const fieldName2 = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
48733
+ if (!fieldName2) return field;
48692
48734
  if (obj.type || obj.inputType) return field;
48693
- const entityField = fieldMap.get(fieldName);
48735
+ const entityField = fieldMap.get(fieldName2);
48694
48736
  if (!entityField) return field;
48695
48737
  const enriched = { ...obj, type: entityField.type };
48696
48738
  if (entityField.required && !("required" in obj)) {
@@ -48740,9 +48782,9 @@ function enrichDetailFields(fields, entityDef) {
48740
48782
  }
48741
48783
  if (field && typeof field === "object" && !Array.isArray(field) && !React85__default.isValidElement(field) && !(field instanceof Date)) {
48742
48784
  const obj = field;
48743
- const fieldName = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
48744
- if (!fieldName || obj.type) return field;
48745
- const meta = metaFor(fieldName);
48785
+ const fieldName2 = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
48786
+ if (!fieldName2 || obj.type) return field;
48787
+ const meta = metaFor(fieldName2);
48746
48788
  return meta ? { ...obj, ...meta } : field;
48747
48789
  }
48748
48790
  return field;
@@ -49581,9 +49623,9 @@ function UISlotRenderer({
49581
49623
  "ui-slot-renderer relative min-h-full",
49582
49624
  className
49583
49625
  ), children: [
49584
- /* @__PURE__ */ jsxs(Box, { className: "flex min-h-full", children: [
49585
- /* @__PURE__ */ jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 shrink-0" }),
49586
- /* @__PURE__ */ jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-h-[200px]" })
49626
+ /* @__PURE__ */ jsxs(Box, { className: "flex min-h-full flex-col lg:flex-row", children: [
49627
+ /* @__PURE__ */ jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 lg:shrink-0" }),
49628
+ /* @__PURE__ */ jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-w-0 min-h-[200px]" })
49587
49629
  ] }),
49588
49630
  /* @__PURE__ */ jsx(UISlotComponent, { slot: "modal", portal: true }),
49589
49631
  /* @__PURE__ */ jsx(UISlotComponent, { slot: "drawer", portal: true }),
@@ -50106,7 +50148,7 @@ function buildTraitRenderConfig(binding, traitConfigsByName, payload) {
50106
50148
  }
50107
50149
  return void 0;
50108
50150
  }
50109
- function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
50151
+ function createSharedEntityWriter(binding, tick, traitStatesRef, emit, traitConfigsByName) {
50110
50152
  return (scratch) => {
50111
50153
  const traitName = binding.trait.name;
50112
50154
  const currentState = traitStatesRef.current.get(traitName)?.currentState ?? "";
@@ -50115,9 +50157,13 @@ function createSharedEntityWriter(binding, tick, traitStatesRef, emit) {
50115
50157
  const writes = [];
50116
50158
  const ctx = createMinimalContext(scratchEntity, {}, currentState);
50117
50159
  const declaredDefaults = collectDeclaredConfigDefaults(binding.trait);
50118
- const callSiteConfig = getBindingConfig(binding);
50119
- if (declaredDefaults || callSiteConfig) {
50120
- ctx.config = { ...declaredDefaults ?? {}, ...callSiteConfig ?? {} };
50160
+ const resolvedByName = traitConfigsByName?.[traitName];
50161
+ const callSiteRaw = getBindingConfig(binding);
50162
+ const callSiteConfig = callSiteRaw ? Object.fromEntries(
50163
+ Object.entries(callSiteRaw).filter(([, v]) => !containsConfigForward(v))
50164
+ ) : void 0;
50165
+ if (declaredDefaults || resolvedByName || callSiteConfig) {
50166
+ ctx.config = { ...declaredDefaults ?? {}, ...resolvedByName ?? {}, ...callSiteConfig ?? {} };
50121
50167
  }
50122
50168
  ctx.mutateEntity = (changes) => {
50123
50169
  for (const [field, value] of Object.entries(changes)) {
@@ -50720,7 +50766,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
50720
50766
  const interval = entries[0].tick.interval;
50721
50767
  const onDue = timedTick(`tick:shared:${group.storeKey}@${String(interval)}`, () => {
50722
50768
  const writers = entries.map(
50723
- ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter)
50769
+ ({ binding, tick }) => createSharedEntityWriter(binding, tick, traitStatesRef, emitFromSharedWriter, traitConfigsByName)
50724
50770
  );
50725
50771
  runTickFrame(group.storeKey, writers, sharedEntityStore);
50726
50772
  });