@almadar/ui 5.160.0 → 5.161.0

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