@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.
@@ -8,7 +8,7 @@ export { ANONYMOUS_USER } from '@almadar/core';
8
8
  import { clsx } from 'clsx';
9
9
  import { twMerge } from 'tailwind-merge';
10
10
  import * as LucideIcons2 from 'lucide-react';
11
- import { Loader2, X, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, Code, FileText, WrapText, Check, Copy, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ArrowLeft, ZoomOut, ZoomIn, Download, DollarSign, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, HelpCircle, PauseCircle, Search, Type, Heading1, Heading2, Heading3, ListOrdered, Quote, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, ChevronUp } from 'lucide-react';
11
+ import { Loader2, X, Lightbulb, CheckCircle, List, Printer, ChevronRight, ChevronLeft, GitBranch, Pencil, Eye, Plus, ArrowRight, Trash, Code, FileText, WrapText, Check, Copy, RotateCcw, Play, Terminal, XCircle, AlertTriangle, Trash2, Link2, ArrowLeft, ZoomOut, ZoomIn, Download, DollarSign, ChevronDown, Menu as Menu$1, Package, Calendar, MoreHorizontal, Image as Image$1, Upload, HelpCircle, PauseCircle, Search, Bold, Italic, Underline, Strikethrough, Heading1, Heading2, Heading3, ListOrdered, Quote, Link as Link$1, Minus, Eraser, TrendingUp, TrendingDown, AlertCircle, Circle, Clock, CheckCircle2, ChevronUp } from 'lucide-react';
12
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
13
  import { createPortal } from 'react-dom';
14
14
  import { useTranslate } from '@almadar/ui/hooks';
@@ -26264,8 +26264,9 @@ function DataGrid({
26264
26264
  const titleField = fieldDefs.find((f3) => f3.variant === "h3" || f3.variant === "h4") ?? fieldDefs[0];
26265
26265
  const badgeFields = fieldDefs.filter((f3) => f3.variant === "badge" && f3 !== titleField);
26266
26266
  const bodyFields = fieldDefs.filter((f3) => f3 !== titleField && !badgeFields.includes(f3));
26267
- const primaryActions = actionDefs.filter((a) => a.variant !== "danger");
26268
- const dangerActions = actionDefs.filter((a) => a.variant === "danger");
26267
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
26268
+ const inlineCardActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
26269
+ const menuCardActions = actionDefs.filter((a) => !inlineCardActions.includes(a));
26269
26270
  const fireAction = (action, itemData) => {
26270
26271
  if (action.navigatesTo) {
26271
26272
  const url = action.navigatesTo.replace(
@@ -26285,6 +26286,9 @@ function DataGrid({
26285
26286
  e.stopPropagation();
26286
26287
  fireAction(action, itemData);
26287
26288
  };
26289
+ const cardClickAction = actionDefs.find((a) => a.variant !== "danger");
26290
+ const handleCardClick = cardClickAction ? (itemData) => () => fireAction(cardClickAction, itemData) : void 0;
26291
+ const stopCardClick = handleCardClick ? (e) => e.stopPropagation() : void 0;
26288
26292
  const hasRenderProp = typeof children === "function";
26289
26293
  useEffect(() => {
26290
26294
  if (data.length > 0 && !hasRenderProp && fieldDefs.length === 0) {
@@ -26356,55 +26360,40 @@ function DataGrid({
26356
26360
  {
26357
26361
  "data-entity-row": true,
26358
26362
  "data-entity-id": id,
26359
- className: cn("relative group/rowactions", isSelected && "ring-2 ring-primary rounded-lg"),
26363
+ onClick: handleCardClick?.(itemData),
26364
+ className: cn("relative group/rowactions", handleCardClick && "cursor-pointer", isSelected && "ring-2 ring-primary rounded-lg"),
26360
26365
  children: [
26361
26366
  children(itemData, index),
26362
- actionDefs.length > 0 && /* @__PURE__ */ jsxs(Box, { className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
26363
- /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: [
26364
- (maxInlineActions != null ? actionDefs.slice(0, maxInlineActions) : actionDefs).map((action, idx) => /* @__PURE__ */ jsxs(
26365
- Button,
26366
- {
26367
- variant: action.variant === "primary" ? "primary" : "ghost",
26368
- size: "sm",
26369
- onClick: handleActionClick(action, itemData),
26370
- "data-testid": `action-${action.event}`,
26371
- "data-row-id": String(itemData.id),
26372
- className: action.variant === "danger" ? "text-error hover:text-error hover:bg-error/10" : void 0,
26373
- children: [
26374
- action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
26375
- action.label
26376
- ]
26377
- },
26378
- idx
26379
- )),
26380
- maxInlineActions != null && actionDefs.length > maxInlineActions && /* @__PURE__ */ jsx(
26381
- Menu,
26382
- {
26383
- position: "bottom-end",
26384
- trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26385
- items: actionDefs.slice(maxInlineActions).map((action) => ({
26386
- label: action.label,
26387
- icon: action.icon,
26388
- event: action.event,
26389
- onClick: () => fireAction(action, itemData)
26390
- }))
26391
- }
26392
- )
26393
- ] }),
26394
- /* @__PURE__ */ jsx(Box, { className: "hidden [@media(pointer:coarse)]:block rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: /* @__PURE__ */ jsx(
26367
+ actionDefs.length > 0 && /* @__PURE__ */ jsx(Box, { onClick: stopCardClick, className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5", children: [
26368
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsxs(
26369
+ Button,
26370
+ {
26371
+ variant: "primary",
26372
+ size: "sm",
26373
+ onClick: handleActionClick(action, itemData),
26374
+ "data-testid": `action-${action.event}`,
26375
+ "data-row-id": String(itemData.id),
26376
+ children: [
26377
+ action.icon && renderIconInput(action.icon, { size: "xs", className: "mr-1" }),
26378
+ action.label
26379
+ ]
26380
+ },
26381
+ idx
26382
+ )),
26383
+ menuCardActions.length > 0 && /* @__PURE__ */ jsx(
26395
26384
  Menu,
26396
26385
  {
26397
26386
  position: "bottom-end",
26398
26387
  trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26399
- items: actionDefs.map((action) => ({
26388
+ items: menuCardActions.map((action) => ({
26400
26389
  label: action.label,
26401
26390
  icon: action.icon,
26402
- event: action.event,
26391
+ variant: action.variant === "danger" ? "danger" : "default",
26403
26392
  onClick: () => fireAction(action, itemData)
26404
26393
  }))
26405
26394
  }
26406
- ) })
26407
- ] })
26395
+ )
26396
+ ] }) })
26408
26397
  ]
26409
26398
  },
26410
26399
  id
@@ -26418,12 +26407,14 @@ function DataGrid({
26418
26407
  {
26419
26408
  "data-entity-row": true,
26420
26409
  "data-entity-id": id,
26410
+ onClick: handleCardClick?.(itemData),
26421
26411
  className: cn(
26422
26412
  "bg-card rounded-lg",
26423
26413
  "border border-border",
26424
26414
  "shadow-elevation-card hover:shadow-elevation-dialog",
26425
26415
  "hover:border-primary transition-all",
26426
26416
  "flex flex-col",
26417
+ handleCardClick && "cursor-pointer",
26427
26418
  isSelected && "ring-2 ring-primary border-primary"
26428
26419
  ),
26429
26420
  children: [
@@ -26473,53 +26464,35 @@ function DataGrid({
26473
26464
  ] }, field.name);
26474
26465
  }) })
26475
26466
  ] }),
26476
- (primaryActions.length > 0 || dangerActions.length > 0) && /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
26477
- (maxInlineActions != null ? primaryActions.slice(0, maxInlineActions) : primaryActions).map((action, idx) => /* @__PURE__ */ jsx(
26467
+ actionDefs.length > 0 && /* @__PURE__ */ jsxs(HStack, { gap: "xs", onClick: stopCardClick, className: "flex-shrink-0", children: [
26468
+ inlineCardActions.map((action, idx) => /* @__PURE__ */ jsx(
26478
26469
  Button,
26479
26470
  {
26480
- variant: action.variant === "primary" ? "primary" : "ghost",
26471
+ variant: "primary",
26481
26472
  size: "sm",
26482
26473
  onClick: handleActionClick(action, itemData),
26483
26474
  "data-testid": `action-${action.event}`,
26484
26475
  "data-row-id": String(itemData.id),
26485
26476
  "aria-label": action.label,
26486
26477
  title: action.label,
26487
- className: cn(
26488
- action.variant === "primary" ? void 0 : "text-muted-foreground hover:text-foreground",
26489
- action.icon && "px-2"
26490
- ),
26478
+ className: cn(action.icon && "px-2"),
26491
26479
  children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
26492
26480
  },
26493
26481
  idx
26494
26482
  )),
26495
- maxInlineActions != null && primaryActions.length > maxInlineActions && /* @__PURE__ */ jsx(
26483
+ menuCardActions.length > 0 && /* @__PURE__ */ jsx(
26496
26484
  Menu,
26497
26485
  {
26498
26486
  position: "bottom-end",
26499
26487
  trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26500
- items: primaryActions.slice(maxInlineActions).map((action) => ({
26488
+ items: menuCardActions.map((action) => ({
26501
26489
  label: action.label,
26502
26490
  icon: action.icon,
26503
- event: action.event,
26491
+ variant: action.variant === "danger" ? "danger" : "default",
26504
26492
  onClick: () => fireAction(action, itemData)
26505
26493
  }))
26506
26494
  }
26507
- ),
26508
- dangerActions.map((action, idx) => /* @__PURE__ */ jsx(
26509
- Button,
26510
- {
26511
- variant: "ghost",
26512
- size: "sm",
26513
- onClick: handleActionClick(action, itemData),
26514
- "data-testid": `action-${action.event}`,
26515
- "data-row-id": String(itemData.id),
26516
- "aria-label": action.label,
26517
- title: action.label,
26518
- className: "text-error hover:text-error hover:bg-error/10 px-2",
26519
- children: action.icon ? renderIconInput(action.icon, { size: "xs" }) : action.label
26520
- },
26521
- `danger-${idx}`
26522
- ))
26495
+ )
26523
26496
  ] })
26524
26497
  ] }) }),
26525
26498
  bodyFields.length > 0 && /* @__PURE__ */ jsx(Box, { className: "px-4 pt-2 pb-4 flex-1", children: /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
@@ -26761,54 +26734,59 @@ function DataList({
26761
26734
  if (!itemActions || itemActions.length === 0) return null;
26762
26735
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
26763
26736
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
26764
- return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "flex-shrink-0", children: [
26765
- inline.map((action, idx) => /* @__PURE__ */ jsxs(
26766
- Button,
26767
- {
26768
- variant: action.variant ?? "ghost",
26769
- size: "sm",
26770
- onClick: handleActionClick(action, itemData),
26771
- "data-testid": `action-${action.event}`,
26772
- "data-row-id": String(itemData.id),
26773
- className: cn(
26774
- action.variant === "danger" && "text-error hover:bg-error/10",
26775
- // Must sit on the Button itself: the variant's own text colour
26776
- // beats an inherited one from the row wrapper.
26777
- onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
26778
- ),
26779
- children: [
26780
- action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
26781
- action.label
26782
- ]
26783
- },
26784
- idx
26785
- )),
26786
- overflow.length > 0 && /* @__PURE__ */ jsx(
26787
- Menu,
26788
- {
26789
- position: "bottom-end",
26790
- trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26791
- items: overflow.map((action) => ({
26792
- label: action.label,
26793
- icon: action.icon,
26794
- event: action.event,
26795
- variant: action.variant === "danger" ? "danger" : "default",
26796
- onClick: () => eventBus.emit(`UI:${action.event}`, {
26797
- id: itemData.id,
26798
- row: itemData
26799
- })
26800
- }))
26801
- }
26802
- )
26803
- ] });
26737
+ return (
26738
+ // stopPropagation at the cluster: the "⋯" trigger opens the overflow
26739
+ // menu without also firing the row's default click (inline buttons
26740
+ // already stop it in handleActionClick; the Menu panel is portaled).
26741
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", onClick: rowClickEvent ? (e) => e.stopPropagation() : void 0, className: "flex-shrink-0", children: [
26742
+ inline.map((action, idx) => /* @__PURE__ */ jsxs(
26743
+ Button,
26744
+ {
26745
+ variant: action.variant ?? "ghost",
26746
+ size: "sm",
26747
+ onClick: handleActionClick(action, itemData),
26748
+ "data-testid": `action-${action.event}`,
26749
+ "data-row-id": String(itemData.id),
26750
+ className: cn(
26751
+ action.variant === "danger" && "text-error hover:bg-error/10",
26752
+ // Must sit on the Button itself: the variant's own text colour
26753
+ // beats an inherited one from the row wrapper.
26754
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
26755
+ ),
26756
+ children: [
26757
+ action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
26758
+ action.label
26759
+ ]
26760
+ },
26761
+ idx
26762
+ )),
26763
+ overflow.length > 0 && /* @__PURE__ */ jsx(
26764
+ Menu,
26765
+ {
26766
+ position: "bottom-end",
26767
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
26768
+ items: overflow.map((action) => ({
26769
+ label: action.label,
26770
+ icon: action.icon,
26771
+ variant: action.variant === "danger" ? "danger" : "default",
26772
+ onClick: () => eventBus.emit(`UI:${action.event}`, {
26773
+ id: itemData.id,
26774
+ row: itemData
26775
+ })
26776
+ }))
26777
+ }
26778
+ )
26779
+ ] })
26780
+ );
26804
26781
  };
26782
+ const rowClickEvent = itemClickEvent || itemActions?.find((a) => a.variant !== "danger")?.event;
26805
26783
  const handleRowClick = (itemData) => () => {
26806
- if (!itemClickEvent) return;
26784
+ if (!rowClickEvent) return;
26807
26785
  const payload = {
26808
26786
  id: itemData.id,
26809
26787
  row: itemData
26810
26788
  };
26811
- eventBus.emit(`UI:${itemClickEvent}`, payload);
26789
+ eventBus.emit(`UI:${rowClickEvent}`, payload);
26812
26790
  };
26813
26791
  if (isLoading) {
26814
26792
  return /* @__PURE__ */ jsx(Box, { className: "text-center py-8", children: /* @__PURE__ */ jsx(Typography, { variant: "body", color: "secondary", children: t("loading.items") }) });
@@ -26856,10 +26834,10 @@ function DataList({
26856
26834
  {
26857
26835
  "data-entity-row": true,
26858
26836
  "data-entity-id": id,
26859
- onClick: itemClickEvent ? handleRowClick(itemData) : void 0,
26837
+ onClick: rowClickEvent ? handleRowClick(itemData) : void 0,
26860
26838
  className: cn(
26861
26839
  "flex px-4 group/rowactions",
26862
- itemClickEvent && "cursor-pointer",
26840
+ rowClickEvent && "cursor-pointer",
26863
26841
  isSent ? "justify-end" : "justify-start"
26864
26842
  ),
26865
26843
  children: /* @__PURE__ */ jsxs(
@@ -26935,7 +26913,7 @@ function DataList({
26935
26913
  const id2 = itemData.id || String(index);
26936
26914
  const actions = renderItemActions(itemData);
26937
26915
  return wrapDnd(
26938
- /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id2, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", itemClickEvent && "cursor-pointer"), children: [
26916
+ /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id2, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn("relative group/rowactions", rowClickEvent && "cursor-pointer"), children: [
26939
26917
  children(itemData, index),
26940
26918
  actions && /* @__PURE__ */ jsxs(Box, { className: "absolute top-2 right-2 z-10 opacity-0 group-hover/rowactions:opacity-100 focus-within:opacity-100 [@media(pointer:coarse)]:opacity-100 transition-opacity duration-fast", children: [
26941
26919
  /* @__PURE__ */ jsx(Box, { className: "rounded-md border border-border bg-card/95 backdrop-blur-sm shadow-sm p-0.5 [@media(pointer:coarse)]:hidden", children: actions }),
@@ -26947,7 +26925,6 @@ function DataList({
26947
26925
  items: (itemActions ?? []).map((action) => ({
26948
26926
  label: action.label,
26949
26927
  icon: action.icon,
26950
- event: action.event,
26951
26928
  variant: action.variant === "danger" ? "danger" : "default",
26952
26929
  onClick: () => eventBus.emit(`UI:${action.event}`, {
26953
26930
  id: itemData.id,
@@ -26964,7 +26941,7 @@ function DataList({
26964
26941
  const id = itemData.id || String(index);
26965
26942
  const titleValue = getNestedValue(itemData, titleField?.name ?? "");
26966
26943
  return wrapDnd(
26967
- /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: itemClickEvent ? handleRowClick(itemData) : void 0, className: cn(itemClickEvent && "cursor-pointer"), children: [
26944
+ /* @__PURE__ */ jsxs(Box, { "data-entity-row": true, "data-entity-id": id, onClick: rowClickEvent ? handleRowClick(itemData) : void 0, className: cn(rowClickEvent && "cursor-pointer"), children: [
26968
26945
  /* @__PURE__ */ jsxs(
26969
26946
  Box,
26970
26947
  {
@@ -32240,7 +32217,7 @@ var init_MapView = __esm({
32240
32217
  shadowSize: [41, 41]
32241
32218
  });
32242
32219
  L.Marker.prototype.options.icon = defaultIcon;
32243
- const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback97, useState: useState95 } = React87__default;
32220
+ const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback97, useState: useState97 } = React87__default;
32244
32221
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
32245
32222
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
32246
32223
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -32285,7 +32262,7 @@ var init_MapView = __esm({
32285
32262
  showAttribution = true
32286
32263
  }) {
32287
32264
  const eventBus = useEventBus2();
32288
- const [clickedPosition, setClickedPosition] = useState95(null);
32265
+ const [clickedPosition, setClickedPosition] = useState97(null);
32289
32266
  const handleMapClick = useCallback97((lat, lng) => {
32290
32267
  if (showClickedPin) {
32291
32268
  setClickedPosition({ lat, lng });
@@ -36647,741 +36624,775 @@ var init_PositionedCanvas = __esm({
36647
36624
  PositionedCanvas.displayName = "PositionedCanvas";
36648
36625
  }
36649
36626
  });
36650
- function nextBlockId(prefix = "blk") {
36651
- _idSeq += 1;
36652
- const random = Math.random().toString(36).slice(2, 8);
36653
- return `${prefix}-${Date.now().toString(36)}-${_idSeq}-${random}`;
36654
- }
36655
- function normalizeBlocks(raw) {
36656
- if (!Array.isArray(raw) || raw.length === 0) return [createBlock("paragraph")];
36657
- return raw.map((row) => {
36658
- const entity = row;
36659
- const rawType = entity.type;
36660
- const type = typeof rawType === "string" && BLOCK_TYPES.has(rawType) ? rawType : "paragraph";
36661
- const id = typeof entity.id === "string" && entity.id ? entity.id : nextBlockId(type);
36662
- return { ...entity, id, type };
36663
- });
36664
- }
36665
- function createBlock(type) {
36666
- switch (type) {
36667
- case "bullet-list":
36668
- case "numbered-list":
36669
- return {
36670
- id: nextBlockId(type),
36671
- type,
36672
- children: [
36673
- { id: nextBlockId("li"), type: "paragraph", content: "" }
36674
- ]
36675
- };
36676
- case "image":
36677
- return {
36678
- id: nextBlockId(type),
36679
- type,
36680
- content: "",
36681
- metadata: { url: "", caption: "" }
36682
- };
36683
- case "code":
36684
- return {
36685
- id: nextBlockId(type),
36686
- type,
36687
- content: "",
36688
- metadata: { language: "plaintext" }
36689
- };
36690
- case "divider":
36691
- return { id: nextBlockId(type), type };
36692
- default:
36693
- return { id: nextBlockId(type), type, content: "" };
36627
+ function safeUrl(raw, kinds) {
36628
+ const url = raw.trim();
36629
+ if (url.startsWith("#") || url.startsWith("/")) return url;
36630
+ const lower = url.toLowerCase();
36631
+ for (const kind of kinds) {
36632
+ if (lower.startsWith(kind)) return url;
36694
36633
  }
36634
+ return null;
36695
36635
  }
36696
- function replaceBlock(blocks, id, updater) {
36697
- return blocks.map((block) => block.id === id ? updater(block) : block);
36698
- }
36699
- function removeBlock(blocks, id) {
36700
- return blocks.filter((block) => block.id !== id);
36701
- }
36702
- function duplicateBlock(block) {
36703
- return {
36704
- ...block,
36705
- id: nextBlockId(block.type),
36706
- children: block.children?.map((child) => ({
36707
- ...child,
36708
- id: nextBlockId("li")
36709
- })),
36710
- metadata: block.metadata ? { ...block.metadata } : void 0
36711
- };
36712
- }
36713
- function insertAfter(blocks, targetId, inserted) {
36714
- const idx = blocks.findIndex((b) => b.id === targetId);
36715
- if (idx === -1) return [...blocks, inserted];
36716
- const next = blocks.slice();
36717
- next.splice(idx + 1, 0, inserted);
36718
- return next;
36719
- }
36720
- function changeBlockType(block, type) {
36721
- if (block.type === type) return block;
36722
- if (type === "bullet-list" || type === "numbered-list") {
36723
- if (block.children && block.children.length > 0) {
36724
- return { ...block, type };
36636
+ function sanitizeNode(el) {
36637
+ const children = Array.from(el.children);
36638
+ for (const child of children) {
36639
+ const tag = child.tagName.toLowerCase();
36640
+ if (DROP_TAGS.has(tag)) {
36641
+ child.remove();
36642
+ continue;
36725
36643
  }
36726
- const seed2 = block.content ?? "";
36727
- return {
36728
- id: block.id,
36729
- type,
36730
- children: [
36731
- { id: nextBlockId("li"), type: "paragraph", content: seed2 }
36732
- ]
36733
- };
36734
- }
36735
- if (type === "divider") {
36736
- return { id: block.id, type };
36644
+ const allowed = ALLOWED_ATTRS[tag];
36645
+ if (!allowed) {
36646
+ sanitizeNode(child);
36647
+ child.replaceWith(...Array.from(child.childNodes));
36648
+ continue;
36649
+ }
36650
+ for (const attr of Array.from(child.attributes)) {
36651
+ const name = attr.name.toLowerCase();
36652
+ if (!allowed.includes(name)) {
36653
+ child.removeAttribute(attr.name);
36654
+ continue;
36655
+ }
36656
+ if (name === "href") {
36657
+ const url = safeUrl(attr.value, ["http://", "https://", "mailto:"]);
36658
+ if (url === null) child.removeAttribute(attr.name);
36659
+ else child.setAttribute("href", url);
36660
+ }
36661
+ if (name === "src") {
36662
+ const url = safeUrl(attr.value, ["http://", "https://", "data:image/"]);
36663
+ if (url === null) child.remove();
36664
+ else child.setAttribute("src", url);
36665
+ }
36666
+ }
36667
+ sanitizeNode(child);
36737
36668
  }
36738
- if (type === "image") {
36739
- return {
36740
- id: block.id,
36741
- type,
36742
- content: "",
36743
- metadata: { url: "", caption: block.content ?? "" }
36744
- };
36669
+ }
36670
+ function sanitizeRichHtml(html) {
36671
+ if (!html || typeof html !== "string") return "";
36672
+ if (typeof window === "undefined" || typeof DOMParser === "undefined") return "";
36673
+ const doc = new DOMParser().parseFromString(html, "text/html");
36674
+ sanitizeNode(doc.body);
36675
+ if (!doc.body.querySelector("img, hr") && (doc.body.textContent ?? "").trim().length === 0) {
36676
+ return "";
36745
36677
  }
36746
- if (type === "code") {
36747
- return {
36748
- id: block.id,
36749
- type,
36750
- content: block.content ?? "",
36751
- metadata: { language: "plaintext" }
36752
- };
36678
+ return doc.body.innerHTML.replace(/​/g, "");
36679
+ }
36680
+ function htmlIsEmpty(el) {
36681
+ if (el.querySelector("img, hr")) return false;
36682
+ return (el.textContent ?? "").trim().length === 0;
36683
+ }
36684
+ function closestBlock(node, root) {
36685
+ let cur = node;
36686
+ while (cur && cur !== root) {
36687
+ if (cur instanceof Element && BLOCK_TAGS.has(cur.tagName)) return cur;
36688
+ cur = cur.parentNode;
36753
36689
  }
36754
- const seed = block.children?.[0]?.content ?? block.content ?? "";
36755
- return { id: block.id, type, content: seed };
36690
+ return null;
36756
36691
  }
36757
- function BlockMenu({ block, readOnly, onDelete, onDuplicate, onChangeType }) {
36758
- const { t } = useTranslate();
36759
- const [open, setOpen] = useState(false);
36760
- const ref = useRef(null);
36761
- useEffect(() => {
36762
- if (!open) return;
36763
- function onDocClick(e) {
36764
- if (ref.current && !ref.current.contains(e.target)) {
36765
- setOpen(false);
36692
+ function RichTextStyles() {
36693
+ return /* @__PURE__ */ jsx("style", { children: RICH_TEXT_CSS });
36694
+ }
36695
+ function readToolbarState(root) {
36696
+ const sel = window.getSelection();
36697
+ if (!sel || sel.rangeCount === 0) return IDLE_TOOLBAR;
36698
+ const anchor = sel.anchorNode;
36699
+ if (!anchor || !root.contains(anchor)) return IDLE_TOOLBAR;
36700
+ let block = null;
36701
+ let link = false;
36702
+ let node = anchor;
36703
+ while (node && node !== root) {
36704
+ if (node instanceof Element) {
36705
+ const tag = node.tagName.toLowerCase();
36706
+ if (!block && (tag === "h1" || tag === "h2" || tag === "h3" || tag === "blockquote" || tag === "pre")) {
36707
+ block = tag;
36766
36708
  }
36709
+ if (tag === "a") link = true;
36767
36710
  }
36768
- document.addEventListener("mousedown", onDocClick);
36769
- return () => document.removeEventListener("mousedown", onDocClick);
36770
- }, [open]);
36771
- if (readOnly) return null;
36772
- return /* @__PURE__ */ jsxs(Box, { ref, className: "relative", children: [
36773
- /* @__PURE__ */ jsx(
36774
- Button,
36775
- {
36776
- type: "button",
36777
- variant: "ghost",
36778
- "aria-label": t("richBlockEditor.blockActions"),
36779
- className: cn(
36780
- "inline-flex items-center justify-center",
36781
- "h-6 w-6 rounded-sm p-0 gap-0",
36782
- "text-muted-foreground hover:bg-muted hover:text-foreground",
36783
- "opacity-0 group-hover:opacity-100 focus-visible:opacity-100",
36784
- "transition-opacity"
36785
- ),
36786
- onClick: () => setOpen((v) => !v),
36787
- children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", className: "w-3.5 h-3.5" })
36788
- }
36789
- ),
36790
- open && /* @__PURE__ */ jsxs(
36791
- Box,
36792
- {
36793
- role: "menu",
36794
- className: cn(
36795
- "absolute right-0 z-10 mt-1 w-44",
36796
- "rounded-container border border-border bg-popover shadow-elevation-popover",
36797
- "py-1 text-sm"
36798
- ),
36799
- children: [
36800
- /* @__PURE__ */ jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t(BLOCK_TYPE_LABEL_KEY[block.type]) }),
36801
- /* @__PURE__ */ jsxs(
36802
- Button,
36803
- {
36804
- type: "button",
36805
- variant: "ghost",
36806
- role: "menuitem",
36807
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
36808
- onClick: () => {
36809
- onDuplicate();
36810
- setOpen(false);
36811
- },
36812
- children: [
36813
- /* @__PURE__ */ jsx(Icon, { name: "plus", className: "w-3.5 h-3.5" }),
36814
- " ",
36815
- t("richBlockEditor.duplicate")
36816
- ]
36817
- }
36818
- ),
36819
- /* @__PURE__ */ jsxs(
36820
- Button,
36821
- {
36822
- type: "button",
36823
- variant: "ghost",
36824
- role: "menuitem",
36825
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left text-destructive hover:bg-muted justify-start rounded-none",
36826
- onClick: () => {
36827
- onDelete();
36828
- setOpen(false);
36829
- },
36830
- children: [
36831
- /* @__PURE__ */ jsx(Icon, { name: "trash", className: "w-3.5 h-3.5" }),
36832
- " ",
36833
- t("common.delete")
36834
- ]
36835
- }
36836
- ),
36837
- CHANGEABLE_TYPES.includes(block.type) && /* @__PURE__ */ jsxs(Fragment, { children: [
36838
- /* @__PURE__ */ jsx(Box, { className: "my-1 border-t border-border" }),
36839
- /* @__PURE__ */ jsx(Box, { className: "px-2 py-1 text-xs uppercase tracking-wide text-muted-foreground", children: t("richBlockEditor.turnInto") }),
36840
- CHANGEABLE_TYPES.filter((bt) => bt !== block.type).map((bt) => /* @__PURE__ */ jsx(
36841
- Button,
36842
- {
36843
- type: "button",
36844
- variant: "ghost",
36845
- role: "menuitem",
36846
- className: "flex w-full items-center gap-2 px-2 py-1.5 text-left justify-start rounded-none",
36847
- onClick: () => {
36848
- onChangeType(bt);
36849
- setOpen(false);
36850
- },
36851
- children: t(BLOCK_TYPE_LABEL_KEY[bt])
36852
- },
36853
- bt
36854
- ))
36855
- ] })
36856
- ]
36857
- }
36858
- )
36859
- ] });
36711
+ node = node.parentNode;
36712
+ }
36713
+ return {
36714
+ bold: document.queryCommandState("bold"),
36715
+ italic: document.queryCommandState("italic"),
36716
+ underline: document.queryCommandState("underline"),
36717
+ strikeThrough: document.queryCommandState("strikeThrough"),
36718
+ block,
36719
+ bullets: document.queryCommandState("insertUnorderedList"),
36720
+ numbers: document.queryCommandState("insertOrderedList"),
36721
+ link
36722
+ };
36860
36723
  }
36861
- function Editable({
36862
- tag,
36863
- value,
36864
- readOnly,
36865
- placeholder,
36866
- className,
36867
- ariaLabel,
36868
- onValueChange
36869
- }) {
36870
- const ref = useRef(null);
36871
- useEffect(() => {
36872
- const el = ref.current;
36873
- if (!el) return;
36874
- const isFocused = document.activeElement === el;
36875
- if (!isFocused && el.textContent !== value) {
36876
- el.textContent = value;
36877
- }
36878
- }, [value]);
36879
- const handleInput = useCallback(
36880
- (e) => {
36881
- onValueChange(e.currentTarget.textContent ?? "");
36882
- },
36883
- [onValueChange]
36884
- );
36724
+ function ToolbarButton({ icon: IconCmp, label, active, onExec }) {
36885
36725
  return /* @__PURE__ */ jsx(
36886
- Box,
36726
+ Button,
36887
36727
  {
36888
- as: tag,
36889
- ref,
36890
- contentEditable: !readOnly,
36891
- suppressContentEditableWarning: true,
36892
- role: readOnly ? void 0 : "textbox",
36893
- "aria-label": ariaLabel,
36894
- "aria-multiline": "true",
36895
- "data-placeholder": placeholder,
36728
+ type: "button",
36729
+ variant: "ghost",
36730
+ size: "sm",
36731
+ "aria-label": label,
36732
+ "aria-pressed": active,
36733
+ title: label,
36896
36734
  className: cn(
36897
- "outline-none focus-visible:ring-1 focus-visible:ring-ring rounded-sm",
36898
- "empty:before:content-[attr(data-placeholder)] empty:before:text-muted-foreground/60",
36899
- className
36735
+ "h-8 w-8 p-0 gap-0 justify-center",
36736
+ active && "bg-muted text-foreground"
36900
36737
  ),
36901
- onInput: handleInput
36738
+ onMouseDown: (e) => {
36739
+ e.preventDefault();
36740
+ onExec();
36741
+ },
36742
+ children: /* @__PURE__ */ jsx(IconCmp, { size: 15 })
36902
36743
  }
36903
36744
  );
36904
36745
  }
36905
- function BlockRow({
36906
- block,
36907
- readOnly,
36908
- showAffordances,
36909
- placeholder,
36910
- onUpdate,
36911
- onDelete,
36912
- onDuplicate,
36913
- onChangeType
36914
- }) {
36915
- const { t } = useTranslate();
36916
- const setContent = useCallback(
36917
- (next) => onUpdate((b) => ({ ...b, content: next })),
36918
- [onUpdate]
36919
- );
36920
- const setMetadata = useCallback(
36921
- (key, value) => onUpdate((b) => ({
36922
- ...b,
36923
- metadata: { ...b.metadata ?? {}, [key]: value }
36924
- })),
36925
- [onUpdate]
36926
- );
36927
- const setChildContent = useCallback(
36928
- (childId, next) => onUpdate((b) => ({
36929
- ...b,
36930
- children: (b.children ?? []).map(
36931
- (c) => c.id === childId ? { ...c, content: next } : c
36932
- )
36933
- })),
36934
- [onUpdate]
36935
- );
36936
- const addListItem = useCallback(
36937
- () => onUpdate((b) => ({
36938
- ...b,
36939
- children: [
36940
- ...b.children ?? [],
36941
- { id: nextBlockId("li"), type: "paragraph", content: "" }
36942
- ]
36943
- })),
36944
- [onUpdate]
36945
- );
36946
- const removeListItem = useCallback(
36947
- (childId) => onUpdate((b) => {
36948
- const remaining = (b.children ?? []).filter((c) => c.id !== childId);
36949
- return {
36950
- ...b,
36951
- children: remaining.length === 0 ? [{ id: nextBlockId("li"), type: "paragraph", content: "" }] : remaining
36952
- };
36953
- }),
36954
- [onUpdate]
36955
- );
36956
- const renderBody = () => {
36957
- switch (block.type) {
36958
- case "heading-1":
36959
- return /* @__PURE__ */ jsx(
36960
- Editable,
36961
- {
36962
- tag: "h1",
36963
- value: block.content ?? "",
36964
- readOnly,
36965
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading1"),
36966
- ariaLabel: t("richBlockEditor.aria.heading1Block"),
36967
- className: "text-3xl font-bold leading-tight",
36968
- onValueChange: setContent
36969
- }
36970
- );
36971
- case "heading-2":
36972
- return /* @__PURE__ */ jsx(
36973
- Editable,
36974
- {
36975
- tag: "h2",
36976
- value: block.content ?? "",
36977
- readOnly,
36978
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading2"),
36979
- ariaLabel: t("richBlockEditor.aria.heading2Block"),
36980
- className: "text-2xl font-semibold leading-tight",
36981
- onValueChange: setContent
36982
- }
36983
- );
36984
- case "heading-3":
36985
- return /* @__PURE__ */ jsx(
36986
- Editable,
36987
- {
36988
- tag: "h3",
36989
- value: block.content ?? "",
36990
- readOnly,
36991
- placeholder: placeholder ?? t("richBlockEditor.placeholder.heading3"),
36992
- ariaLabel: t("richBlockEditor.aria.heading3Block"),
36993
- className: "text-xl font-semibold leading-tight",
36994
- onValueChange: setContent
36995
- }
36996
- );
36997
- case "quote":
36998
- return /* @__PURE__ */ jsx(
36999
- Editable,
37000
- {
37001
- tag: "blockquote",
37002
- value: block.content ?? "",
37003
- readOnly,
37004
- placeholder: placeholder ?? t("richBlockEditor.placeholder.quote"),
37005
- ariaLabel: t("richBlockEditor.aria.quoteBlock"),
37006
- className: "border-l-4 border-primary/60 pl-4 italic text-muted-foreground",
37007
- onValueChange: setContent
37008
- }
37009
- );
37010
- case "code":
37011
- return /* @__PURE__ */ jsxs(Box, { className: "rounded-md border border-border bg-muted/40", children: [
37012
- /* @__PURE__ */ jsxs(Box, { className: "flex items-center justify-between border-b border-border px-3 py-1 text-xs text-muted-foreground", children: [
37013
- /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "uppercase tracking-wide", children: t("richBlockEditor.blockType.code") }),
37014
- !readOnly && /* @__PURE__ */ jsx(
37015
- Input,
37016
- {
37017
- inputType: "text",
37018
- value: String(block.metadata?.language ?? "plaintext"),
37019
- "aria-label": t("richBlockEditor.aria.codeLanguage"),
37020
- className: cn(
37021
- "h-6 w-32 rounded-sm border border-border bg-background",
37022
- "px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
37023
- ),
37024
- onChange: (e) => setMetadata("language", e.target.value)
37025
- }
37026
- ),
37027
- readOnly && /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "text-xs", children: String(block.metadata?.language ?? "plaintext") })
37028
- ] }),
37029
- /* @__PURE__ */ jsx(
37030
- Editable,
37031
- {
37032
- tag: "pre",
37033
- value: block.content ?? "",
37034
- readOnly,
37035
- placeholder: placeholder ?? t("richBlockEditor.placeholder.code"),
37036
- ariaLabel: t("richBlockEditor.aria.codeBlock"),
37037
- className: "block whitespace-pre-wrap p-3 font-mono text-sm leading-relaxed",
37038
- onValueChange: setContent
37039
- }
37040
- )
37041
- ] });
37042
- case "divider":
37043
- return /* @__PURE__ */ jsx(Divider, { className: "my-2" });
37044
- case "image": {
37045
- const url = String(block.metadata?.url ?? "");
37046
- const caption = String(block.metadata?.caption ?? "");
37047
- const imgProps = {
37048
- src: url,
37049
- alt: caption || t("richBlockEditor.embeddedImage"),
37050
- className: "max-h-96 w-full rounded-md border border-border object-contain"
36746
+ var CHANGE_DEBOUNCE_MS, ALLOWED_ATTRS, DROP_TAGS, MD_BLOCK_PREFIXES, BLOCK_TAGS, MD_INLINE_RULES, RICH_TEXT_CSS, IDLE_TOOLBAR, RichTextEditor;
36747
+ var init_RichTextEditor = __esm({
36748
+ "components/core/molecules/RichTextEditor.tsx"() {
36749
+ "use client";
36750
+ init_cn();
36751
+ init_Box();
36752
+ init_Button();
36753
+ init_useEventBus();
36754
+ CHANGE_DEBOUNCE_MS = 400;
36755
+ ALLOWED_ATTRS = {
36756
+ p: [],
36757
+ h1: [],
36758
+ h2: [],
36759
+ h3: [],
36760
+ h4: [],
36761
+ ul: [],
36762
+ ol: [],
36763
+ li: [],
36764
+ blockquote: [],
36765
+ pre: [],
36766
+ code: [],
36767
+ b: [],
36768
+ strong: [],
36769
+ i: [],
36770
+ em: [],
36771
+ u: [],
36772
+ s: [],
36773
+ strike: [],
36774
+ br: [],
36775
+ hr: [],
36776
+ div: [],
36777
+ span: [],
36778
+ a: ["href"],
36779
+ img: ["src", "alt"]
36780
+ };
36781
+ DROP_TAGS = /* @__PURE__ */ new Set(["script", "style", "iframe", "object", "embed", "link", "meta", "form", "input", "button", "textarea", "select"]);
36782
+ MD_BLOCK_PREFIXES = {
36783
+ "#": { kind: "block", tag: "h1" },
36784
+ "##": { kind: "block", tag: "h2" },
36785
+ "###": { kind: "block", tag: "h3" },
36786
+ ">": { kind: "block", tag: "blockquote" },
36787
+ "```": { kind: "block", tag: "pre" },
36788
+ "-": { kind: "list", command: "insertUnorderedList" },
36789
+ "*": { kind: "list", command: "insertUnorderedList" },
36790
+ "1.": { kind: "list", command: "insertOrderedList" }
36791
+ };
36792
+ BLOCK_TAGS = /* @__PURE__ */ new Set(["P", "DIV", "LI", "H1", "H2", "H3", "H4", "BLOCKQUOTE", "PRE"]);
36793
+ MD_INLINE_RULES = [
36794
+ { pattern: /\*\*([^*\n]+)\*\*$/, wrap: "strong" },
36795
+ { pattern: /(^|[^*])\*([^*\n]+)\*$/, wrap: "em" },
36796
+ { pattern: /`([^`\n]+)`$/, wrap: "code" }
36797
+ ];
36798
+ RICH_TEXT_CSS = `
36799
+ .almadar-rich-text { line-height: 1.7; color: var(--color-foreground); }
36800
+ .almadar-rich-text h1 { font-size: 1.875rem; font-weight: 700; line-height: 1.25; margin: 1.25em 0 0.4em; }
36801
+ .almadar-rich-text h2 { font-size: 1.5rem; font-weight: 650; line-height: 1.3; margin: 1.1em 0 0.4em; }
36802
+ .almadar-rich-text h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.35; margin: 1em 0 0.35em; }
36803
+ .almadar-rich-text h1:first-child, .almadar-rich-text h2:first-child, .almadar-rich-text h3:first-child { margin-top: 0; }
36804
+ .almadar-rich-text p { margin: 0.5em 0; }
36805
+ .almadar-rich-text ul { list-style: disc; padding-inline-start: 1.5rem; margin: 0.5em 0; }
36806
+ .almadar-rich-text ol { list-style: decimal; padding-inline-start: 1.5rem; margin: 0.5em 0; }
36807
+ .almadar-rich-text li { margin: 0.25em 0; }
36808
+ .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; }
36809
+ .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; }
36810
+ .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; }
36811
+ .almadar-rich-text pre code { background: transparent; padding: 0; }
36812
+ .almadar-rich-text a { color: var(--color-primary); text-decoration: underline; }
36813
+ .almadar-rich-text hr { border: none; border-top: 1px solid var(--color-border); margin: 1.25em 0; }
36814
+ .almadar-rich-text img { max-width: 100%; border-radius: 0.375rem; }
36815
+ `;
36816
+ IDLE_TOOLBAR = {
36817
+ bold: false,
36818
+ italic: false,
36819
+ underline: false,
36820
+ strikeThrough: false,
36821
+ block: null,
36822
+ bullets: false,
36823
+ numbers: false,
36824
+ link: false
36825
+ };
36826
+ RichTextEditor = ({
36827
+ value,
36828
+ onChange,
36829
+ changeEvent,
36830
+ readOnly = false,
36831
+ placeholder,
36832
+ showToolbar = true,
36833
+ className
36834
+ }) => {
36835
+ const { t } = useTranslate();
36836
+ const ref = useRef(null);
36837
+ const [toolbar, setToolbar] = useState(IDLE_TOOLBAR);
36838
+ const [empty, setEmpty] = useState(() => !value || !sanitizeRichHtml(value).trim());
36839
+ const eventBus = useEventBus();
36840
+ const onChangeRef = useRef(onChange);
36841
+ const changeEventRef = useRef(changeEvent);
36842
+ useEffect(() => {
36843
+ onChangeRef.current = onChange;
36844
+ changeEventRef.current = changeEvent;
36845
+ }, [onChange, changeEvent]);
36846
+ const emitTimerRef = useRef(null);
36847
+ const emitNow = useCallback(() => {
36848
+ const el = ref.current;
36849
+ if (!el) return;
36850
+ const html = sanitizeRichHtml(el.innerHTML);
36851
+ onChangeRef.current?.(html);
36852
+ const evt = changeEventRef.current;
36853
+ if (evt) eventBus.emit(`UI:${evt}`, { value: html });
36854
+ }, [eventBus]);
36855
+ const scheduleEmit = useCallback(() => {
36856
+ if (emitTimerRef.current) clearTimeout(emitTimerRef.current);
36857
+ emitTimerRef.current = setTimeout(() => {
36858
+ emitTimerRef.current = null;
36859
+ emitNow();
36860
+ }, CHANGE_DEBOUNCE_MS);
36861
+ }, [emitNow]);
36862
+ const flushEmit = useCallback(() => {
36863
+ if (!emitTimerRef.current) return;
36864
+ clearTimeout(emitTimerRef.current);
36865
+ emitTimerRef.current = null;
36866
+ emitNow();
36867
+ }, [emitNow]);
36868
+ useEffect(() => () => {
36869
+ if (emitTimerRef.current) {
36870
+ clearTimeout(emitTimerRef.current);
36871
+ emitTimerRef.current = null;
36872
+ emitNow();
36873
+ }
36874
+ }, [emitNow]);
36875
+ useEffect(() => {
36876
+ if (readOnly) return;
36877
+ const el = ref.current;
36878
+ if (!el) return;
36879
+ if (document.activeElement === el) return;
36880
+ const next = sanitizeRichHtml(value ?? "") || "<p><br></p>";
36881
+ if (el.innerHTML !== next) {
36882
+ el.innerHTML = next;
36883
+ setEmpty(htmlIsEmpty(el));
36884
+ }
36885
+ }, [readOnly, value]);
36886
+ useEffect(() => {
36887
+ if (readOnly) return;
36888
+ document.execCommand("defaultParagraphSeparator", false, "p");
36889
+ }, [readOnly]);
36890
+ useEffect(() => {
36891
+ if (readOnly || !showToolbar) return;
36892
+ const onSelectionChange = () => {
36893
+ const el = ref.current;
36894
+ if (!el) return;
36895
+ setToolbar(readToolbarState(el));
37051
36896
  };
37052
- return /* @__PURE__ */ jsxs(Box, { className: "space-y-2", children: [
37053
- url ? /* @__PURE__ */ jsx(Box, { as: "img", ...imgProps }) : /* @__PURE__ */ jsxs(
37054
- Box,
37055
- {
37056
- className: cn(
37057
- "flex h-32 items-center justify-center",
37058
- "rounded-md border border-dashed border-border",
37059
- "text-sm text-muted-foreground"
37060
- ),
37061
- children: [
37062
- /* @__PURE__ */ jsx(Icon, { name: "image", className: "mr-2 w-4 h-4" }),
37063
- " ",
37064
- t("richBlockEditor.noImageUrl")
37065
- ]
37066
- }
37067
- ),
37068
- !readOnly && /* @__PURE__ */ jsxs(Box, { className: "flex flex-col gap-2 sm:flex-row", children: [
37069
- /* @__PURE__ */ jsx(
37070
- Input,
37071
- {
37072
- inputType: "url",
37073
- value: url,
37074
- placeholder: "https://example.com/image.png",
37075
- "aria-label": t("richBlockEditor.aria.imageUrl"),
37076
- className: cn(
37077
- "h-8 flex-1 rounded-sm border border-border bg-background",
37078
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
37079
- ),
37080
- onChange: (e) => setMetadata("url", e.target.value)
37081
- }
37082
- ),
37083
- /* @__PURE__ */ jsx(
37084
- Input,
37085
- {
37086
- inputType: "text",
37087
- value: caption,
37088
- placeholder: t("richBlockEditor.placeholder.caption"),
37089
- "aria-label": t("richBlockEditor.aria.imageCaption"),
37090
- className: cn(
37091
- "h-8 flex-1 rounded-sm border border-border bg-background",
37092
- "px-2 text-sm outline-none focus:ring-1 focus:ring-ring"
37093
- ),
37094
- onChange: (e) => setMetadata("caption", e.target.value)
37095
- }
37096
- )
37097
- ] }),
37098
- readOnly && caption && /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-center text-muted-foreground", children: caption })
36897
+ document.addEventListener("selectionchange", onSelectionChange);
36898
+ return () => document.removeEventListener("selectionchange", onSelectionChange);
36899
+ }, [readOnly, showToolbar]);
36900
+ const tryInlineMarkdown = useCallback(() => {
36901
+ const root = ref.current;
36902
+ if (!root) return false;
36903
+ const sel = window.getSelection();
36904
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
36905
+ const node = sel.anchorNode;
36906
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
36907
+ if (node.parentElement?.closest("code, pre")) return false;
36908
+ const text = (node.textContent ?? "").slice(0, sel.anchorOffset);
36909
+ for (const rule of MD_INLINE_RULES) {
36910
+ const m = rule.pattern.exec(text);
36911
+ if (!m) continue;
36912
+ const inner = rule.wrap === "em" ? m[2] : m[1];
36913
+ if (!inner || !inner.trim()) continue;
36914
+ const lead = rule.wrap === "em" ? m[1]?.length ?? 0 : 0;
36915
+ const range = document.createRange();
36916
+ range.setStart(node, m.index + lead);
36917
+ range.setEnd(node, sel.anchorOffset);
36918
+ const el = document.createElement(rule.wrap);
36919
+ el.textContent = inner;
36920
+ range.deleteContents();
36921
+ range.insertNode(el);
36922
+ const after = document.createTextNode("\u200B");
36923
+ el.parentNode?.insertBefore(after, el.nextSibling);
36924
+ const caret = document.createRange();
36925
+ caret.setStart(after, 1);
36926
+ caret.collapse(true);
36927
+ sel.removeAllRanges();
36928
+ sel.addRange(caret);
36929
+ return true;
36930
+ }
36931
+ return false;
36932
+ }, []);
36933
+ const tryBlockMarkdown = useCallback(() => {
36934
+ const root = ref.current;
36935
+ if (!root) return false;
36936
+ const sel = window.getSelection();
36937
+ if (!sel || !sel.isCollapsed || sel.rangeCount === 0) return false;
36938
+ const node = sel.anchorNode;
36939
+ if (!node || node.nodeType !== Node.TEXT_NODE || !root.contains(node)) return false;
36940
+ const block = closestBlock(node, root);
36941
+ if (block ? block.firstChild !== node : root.firstChild !== node) return false;
36942
+ const prefix = (node.textContent ?? "").slice(0, sel.anchorOffset);
36943
+ const rule = MD_BLOCK_PREFIXES[prefix];
36944
+ if (!rule) return false;
36945
+ if (node.parentElement?.closest("pre, code")) return false;
36946
+ if (rule.kind === "list" && node.parentElement?.closest("li")) return false;
36947
+ node.deleteData(0, prefix.length);
36948
+ const caret = document.createRange();
36949
+ caret.setStart(node, 0);
36950
+ caret.collapse(true);
36951
+ sel.removeAllRanges();
36952
+ sel.addRange(caret);
36953
+ if (rule.kind === "list") {
36954
+ const container = block ?? node.parentElement;
36955
+ const list = document.createElement(rule.command === "insertOrderedList" ? "ol" : "ul");
36956
+ const li = document.createElement("li");
36957
+ if (block) {
36958
+ while (block.firstChild) li.appendChild(block.firstChild);
36959
+ list.appendChild(li);
36960
+ block.replaceWith(list);
36961
+ } else if (container) {
36962
+ li.appendChild(node);
36963
+ list.appendChild(li);
36964
+ container.appendChild(list);
36965
+ }
36966
+ if (!li.firstChild) li.appendChild(document.createElement("br"));
36967
+ const liCaret = document.createRange();
36968
+ liCaret.selectNodeContents(li);
36969
+ liCaret.collapse(true);
36970
+ sel.removeAllRanges();
36971
+ sel.addRange(liCaret);
36972
+ } else {
36973
+ document.execCommand("formatBlock", false, rule.tag);
36974
+ }
36975
+ return true;
36976
+ }, []);
36977
+ const afterEdit = useCallback(() => {
36978
+ const el = ref.current;
36979
+ if (!el) return;
36980
+ tryInlineMarkdown();
36981
+ setEmpty(htmlIsEmpty(el));
36982
+ setToolbar(readToolbarState(el));
36983
+ scheduleEmit();
36984
+ }, [scheduleEmit, tryInlineMarkdown]);
36985
+ const handleKeyDown = useCallback((e) => {
36986
+ if (e.key === " " && tryBlockMarkdown()) {
36987
+ e.preventDefault();
36988
+ afterEdit();
36989
+ }
36990
+ }, [afterEdit, tryBlockMarkdown]);
36991
+ const execInline = useCallback((command) => {
36992
+ document.execCommand(command, false);
36993
+ afterEdit();
36994
+ }, [afterEdit]);
36995
+ const execBlock = useCallback((tag) => {
36996
+ const next = toolbar.block === tag ? "p" : tag;
36997
+ document.execCommand("formatBlock", false, next);
36998
+ afterEdit();
36999
+ }, [afterEdit, toolbar.block]);
37000
+ const execList = useCallback((command) => {
37001
+ document.execCommand(command, false);
37002
+ afterEdit();
37003
+ }, [afterEdit]);
37004
+ const execLink = useCallback(() => {
37005
+ if (toolbar.link) {
37006
+ document.execCommand("unlink", false);
37007
+ afterEdit();
37008
+ return;
37009
+ }
37010
+ const url = window.prompt(t("richTextEditor.linkPrompt"));
37011
+ if (!url) return;
37012
+ const safe = safeUrl(url, ["http://", "https://", "mailto:"]) ?? `https://${url}`;
37013
+ document.execCommand("createLink", false, safe);
37014
+ afterEdit();
37015
+ }, [afterEdit, t, toolbar.link]);
37016
+ const execRule = useCallback(() => {
37017
+ document.execCommand("insertHorizontalRule", false);
37018
+ afterEdit();
37019
+ }, [afterEdit]);
37020
+ if (readOnly) {
37021
+ return /* @__PURE__ */ jsxs(Box, { className: cn("almadar-rich-text max-w-none", className), children: [
37022
+ /* @__PURE__ */ jsx(RichTextStyles, {}),
37023
+ /* @__PURE__ */ jsx(Box, { dangerouslySetInnerHTML: { __html: sanitizeRichHtml(value ?? "") } })
37099
37024
  ] });
37100
37025
  }
37101
- case "bullet-list":
37102
- case "numbered-list": {
37103
- const items = block.children ?? [];
37104
- return /* @__PURE__ */ jsxs(
37026
+ return /* @__PURE__ */ jsxs(Box, { className: cn("flex flex-col gap-2", className), children: [
37027
+ /* @__PURE__ */ jsx(RichTextStyles, {}),
37028
+ showToolbar && /* @__PURE__ */ jsxs(
37105
37029
  Box,
37106
37030
  {
37107
- as: block.type === "bullet-list" ? "ul" : "ol",
37031
+ role: "toolbar",
37032
+ "aria-label": t("richTextEditor.editorToolbar"),
37108
37033
  className: cn(
37109
- "space-y-1 pl-6",
37110
- block.type === "bullet-list" ? "list-disc" : "list-decimal"
37034
+ "sticky top-0 z-10 flex flex-wrap items-center gap-0.5 self-start",
37035
+ "rounded-md border border-border bg-background/95 px-1 py-0.5 shadow-sm"
37111
37036
  ),
37112
37037
  children: [
37113
- items.map((child) => /* @__PURE__ */ jsxs(Box, { as: "li", className: "group/item flex items-start gap-2", children: [
37114
- /* @__PURE__ */ jsx(
37115
- Editable,
37116
- {
37117
- tag: "span",
37118
- value: child.content ?? "",
37119
- readOnly,
37120
- placeholder: t("richBlockEditor.placeholder.listItem"),
37121
- ariaLabel: t("richBlockEditor.aria.listItem"),
37122
- className: "inline-block min-w-[1ch] flex-1",
37123
- onValueChange: (next) => setChildContent(child.id, next)
37124
- }
37125
- ),
37126
- !readOnly && showAffordances && /* @__PURE__ */ jsx(
37127
- Button,
37128
- {
37129
- type: "button",
37130
- variant: "ghost",
37131
- "aria-label": t("richBlockEditor.aria.removeListItem"),
37132
- className: cn(
37133
- "h-5 w-5 shrink-0 rounded-sm text-muted-foreground p-0 gap-0",
37134
- "opacity-0 group-hover/item:opacity-100 hover:bg-muted hover:text-foreground"
37135
- ),
37136
- onClick: () => removeListItem(child.id),
37137
- children: /* @__PURE__ */ jsx(Icon, { name: "trash", className: "w-3 h-3" })
37138
- }
37139
- )
37140
- ] }, child.id)),
37141
- !readOnly && showAffordances && /* @__PURE__ */ jsx(Box, { as: "li", className: "list-none pl-0", children: /* @__PURE__ */ jsxs(
37142
- Button,
37143
- {
37144
- type: "button",
37145
- variant: "ghost",
37146
- className: cn(
37147
- "inline-flex items-center gap-1 text-xs text-muted-foreground",
37148
- "hover:text-foreground p-0 h-auto"
37149
- ),
37150
- onClick: addListItem,
37151
- children: [
37152
- /* @__PURE__ */ jsx(Icon, { name: "plus", className: "w-3 h-3" }),
37153
- " ",
37154
- t("richBlockEditor.addItem")
37155
- ]
37156
- }
37157
- ) })
37038
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Bold, label: t("richTextEditor.bold"), active: toolbar.bold, onExec: () => execInline("bold") }),
37039
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Italic, label: t("richTextEditor.italic"), active: toolbar.italic, onExec: () => execInline("italic") }),
37040
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Underline, label: t("richTextEditor.underline"), active: toolbar.underline, onExec: () => execInline("underline") }),
37041
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Strikethrough, label: t("richTextEditor.strikethrough"), active: toolbar.strikeThrough, onExec: () => execInline("strikeThrough") }),
37042
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
37043
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading1, label: t("richTextEditor.heading1"), active: toolbar.block === "h1", onExec: () => execBlock("h1") }),
37044
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading2, label: t("richTextEditor.heading2"), active: toolbar.block === "h2", onExec: () => execBlock("h2") }),
37045
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Heading3, label: t("richTextEditor.heading3"), active: toolbar.block === "h3", onExec: () => execBlock("h3") }),
37046
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
37047
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: List, label: t("richTextEditor.bulletList"), active: toolbar.bullets, onExec: () => execList("insertUnorderedList") }),
37048
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: ListOrdered, label: t("richTextEditor.numberedList"), active: toolbar.numbers, onExec: () => execList("insertOrderedList") }),
37049
+ /* @__PURE__ */ jsx(Box, { className: "mx-1 h-5 w-px bg-border" }),
37050
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
37051
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
37052
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Link$1, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
37053
+ /* @__PURE__ */ jsx(ToolbarButton, { icon: Minus, label: t("richTextEditor.divider"), onExec: execRule })
37158
37054
  ]
37159
37055
  }
37160
- );
37161
- }
37162
- case "paragraph":
37163
- default:
37164
- return /* @__PURE__ */ jsx(
37165
- Editable,
37056
+ ),
37057
+ /* @__PURE__ */ jsx(
37058
+ Box,
37166
37059
  {
37167
- tag: "p",
37168
- value: block.content ?? "",
37169
- readOnly,
37170
- placeholder: placeholder ?? t("richBlockEditor.placeholder.paragraph"),
37171
- ariaLabel: t("richBlockEditor.aria.paragraphBlock"),
37172
- className: "leading-7",
37173
- onValueChange: setContent
37060
+ ref,
37061
+ contentEditable: true,
37062
+ suppressContentEditableWarning: true,
37063
+ role: "textbox",
37064
+ "aria-multiline": "true",
37065
+ "aria-label": t("richTextEditor.editorSurface"),
37066
+ "data-placeholder": placeholder ?? t("richTextEditor.placeholder"),
37067
+ "data-empty": empty ? "true" : "false",
37068
+ className: cn(
37069
+ "almadar-rich-text max-w-none min-h-[8rem] outline-none",
37070
+ "data-[empty=true]:before:content-[attr(data-placeholder)]",
37071
+ "data-[empty=true]:before:text-muted-foreground/60",
37072
+ "data-[empty=true]:before:pointer-events-none",
37073
+ "data-[empty=true]:before:absolute",
37074
+ "relative cursor-text"
37075
+ ),
37076
+ onInput: afterEdit,
37077
+ onKeyDown: handleKeyDown,
37078
+ onBlur: flushEmit
37174
37079
  }
37175
- );
37176
- }
37080
+ )
37081
+ ] });
37082
+ };
37083
+ RichTextEditor.displayName = "RichTextEditor";
37084
+ }
37085
+ });
37086
+ function renderIconInput4(icon, props) {
37087
+ return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
37088
+ }
37089
+ function DocumentPanel({
37090
+ entity,
37091
+ title,
37092
+ subtitle,
37093
+ value,
37094
+ actions,
37095
+ maxInlineActions,
37096
+ editing = false,
37097
+ titleCommitEvent,
37098
+ contentChangeEvent,
37099
+ editEvent,
37100
+ doneEvent,
37101
+ editLabel,
37102
+ doneLabel,
37103
+ autosaveHint,
37104
+ placeholder,
37105
+ className
37106
+ }) {
37107
+ const eventBus = useEventBus();
37108
+ const { t } = useTranslate();
37109
+ const [titleDraft, setTitleDraft] = useState(null);
37110
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
37111
+ const actionDefs = actions ?? [];
37112
+ const inlineCap = Math.min(maxInlineActions ?? 1, 1);
37113
+ const inlineActions = actionDefs.filter((a) => a.variant === "primary").slice(0, inlineCap);
37114
+ const menuActions = actionDefs.filter((a) => !inlineActions.includes(a));
37115
+ const fireAction = (action) => {
37116
+ if (!action.event) return;
37117
+ eventBus.emit(`UI:${action.event}`, { id: recordId, row: entity });
37177
37118
  };
37178
- return /* @__PURE__ */ jsxs(
37119
+ const commitTitle = () => {
37120
+ if (!titleCommitEvent) return;
37121
+ const next = (titleDraft ?? "").trim();
37122
+ setTitleDraft(null);
37123
+ if (!next || next === title) return;
37124
+ eventBus.emit(`UI:${titleCommitEvent}`, { title: next, id: recordId });
37125
+ };
37126
+ const untitled = t("documentPanel.untitled") || "Untitled";
37127
+ const titleNode = titleCommitEvent && titleDraft !== null ? /* @__PURE__ */ jsx(
37128
+ Input,
37129
+ {
37130
+ value: titleDraft,
37131
+ autoFocus: true,
37132
+ "aria-label": t("common.title"),
37133
+ className: "h-auto py-1 text-3xl font-bold tracking-tight",
37134
+ onFocus: (e) => e.currentTarget.select(),
37135
+ onChange: (e) => setTitleDraft(e.target.value),
37136
+ onBlur: commitTitle,
37137
+ onKeyDown: (e) => {
37138
+ if (e.key === "Enter") {
37139
+ e.preventDefault();
37140
+ commitTitle();
37141
+ } else if (e.key === "Escape") {
37142
+ e.preventDefault();
37143
+ setTitleDraft(null);
37144
+ }
37145
+ },
37146
+ "data-testid": "document-title-input"
37147
+ }
37148
+ ) : titleCommitEvent ? /* @__PURE__ */ jsx(
37179
37149
  Box,
37180
37150
  {
37181
- className: cn(
37182
- "group relative flex items-start gap-2 rounded-sm",
37183
- "px-2 py-1 hover:bg-muted/30"
37184
- ),
37185
- "data-block-id": block.id,
37186
- "data-block-type": block.type,
37187
- children: [
37188
- !readOnly && showAffordances && /* @__PURE__ */ jsx(Box, { className: "flex w-8 shrink-0 items-center pt-1", children: /* @__PURE__ */ jsx(
37189
- BlockMenu,
37151
+ role: "button",
37152
+ tabIndex: 0,
37153
+ className: "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
37154
+ onClick: () => setTitleDraft(title ?? ""),
37155
+ onKeyDown: (e) => {
37156
+ if (e.key === "Enter" || e.key === " ") {
37157
+ e.preventDefault();
37158
+ setTitleDraft(title ?? "");
37159
+ }
37160
+ },
37161
+ "data-testid": "document-title-editable",
37162
+ children: /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", className: cn(!title && "text-muted-foreground"), children: title || untitled })
37163
+ }
37164
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "h2", weight: "bold", children: title || untitled });
37165
+ const emitWithId = (event) => () => {
37166
+ if (event) eventBus.emit(`UI:${event}`, { id: recordId });
37167
+ };
37168
+ return /* @__PURE__ */ jsx(Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
37169
+ /* @__PURE__ */ jsxs(HStack, { gap: "sm", className: "justify-between items-start", children: [
37170
+ /* @__PURE__ */ jsxs(VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
37171
+ titleNode,
37172
+ subtitle ? /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: subtitle }) : null
37173
+ ] }),
37174
+ /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
37175
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
37176
+ /* @__PURE__ */ jsx(Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
37177
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
37178
+ editEvent && /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
37179
+ /* @__PURE__ */ jsx(Icon, { name: "edit", size: "xs", className: "mr-1" }),
37180
+ editLabel ?? (t("documentPanel.edit") || "Edit")
37181
+ ] }),
37182
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxs(
37183
+ Button,
37190
37184
  {
37191
- block,
37192
- readOnly,
37193
- onDelete,
37194
- onDuplicate,
37195
- onChangeType
37185
+ variant: "primary",
37186
+ size: "sm",
37187
+ onClick: () => fireAction(action),
37188
+ "data-testid": `action-${action.event}`,
37189
+ children: [
37190
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
37191
+ action.label
37192
+ ]
37193
+ },
37194
+ idx
37195
+ )),
37196
+ menuActions.length > 0 && /* @__PURE__ */ jsx(
37197
+ Menu,
37198
+ {
37199
+ position: "bottom-end",
37200
+ trigger: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: "xs" }) }),
37201
+ items: menuActions.map((action) => ({
37202
+ label: action.label,
37203
+ icon: action.icon,
37204
+ variant: action.variant === "danger" ? "danger" : "default",
37205
+ onClick: () => fireAction(action)
37206
+ }))
37196
37207
  }
37197
- ) }),
37198
- /* @__PURE__ */ jsx(Box, { className: "min-w-0 flex-1", children: renderBody() })
37199
- ]
37200
- }
37201
- );
37208
+ )
37209
+ ] }) })
37210
+ ] }),
37211
+ editing ? /* @__PURE__ */ jsx(RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsx(
37212
+ Box,
37213
+ {
37214
+ onClick: emitWithId(editEvent),
37215
+ className: cn(
37216
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
37217
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
37218
+ ),
37219
+ "data-testid": "document-body",
37220
+ children: value && value !== "" ? /* @__PURE__ */ jsx(RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsx(Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
37221
+ }
37222
+ )
37223
+ ] }) });
37202
37224
  }
37203
- var TOOLBAR_ENTRIES, BLOCK_TYPE_LABEL_KEY, CHANGEABLE_TYPES, _idSeq, BLOCK_TYPES, RichBlockEditor;
37204
- var init_RichBlockEditor = __esm({
37205
- "components/core/molecules/RichBlockEditor.tsx"() {
37225
+ var init_DocumentPanel = __esm({
37226
+ "components/core/molecules/DocumentPanel.tsx"() {
37206
37227
  "use client";
37207
37228
  init_cn();
37208
- init_Card();
37229
+ init_useEventBus();
37230
+ init_Box();
37231
+ init_Stack();
37209
37232
  init_Typography();
37210
37233
  init_Button();
37211
- init_Box();
37212
- init_Divider();
37213
- init_Input();
37234
+ init_Card();
37214
37235
  init_Icon();
37215
- init_useEventBus();
37216
- TOOLBAR_ENTRIES = [
37217
- { type: "paragraph", labelKey: "richBlockEditor.toolbar.text", icon: Type },
37218
- { type: "heading-1", labelKey: "richBlockEditor.toolbar.h1", icon: Heading1 },
37219
- { type: "heading-2", labelKey: "richBlockEditor.toolbar.h2", icon: Heading2 },
37220
- { type: "heading-3", labelKey: "richBlockEditor.toolbar.h3", icon: Heading3 },
37221
- { type: "bullet-list", labelKey: "richBlockEditor.toolbar.bulletList", icon: List },
37222
- { type: "numbered-list", labelKey: "richBlockEditor.toolbar.numbered", icon: ListOrdered },
37223
- { type: "quote", labelKey: "richBlockEditor.toolbar.quote", icon: Quote },
37224
- { type: "code", labelKey: "richBlockEditor.toolbar.code", icon: Code },
37225
- { type: "divider", labelKey: "richBlockEditor.toolbar.divider", icon: Minus },
37226
- { type: "image", labelKey: "richBlockEditor.toolbar.image", icon: Image$1 }
37227
- ];
37228
- BLOCK_TYPE_LABEL_KEY = {
37229
- paragraph: "richBlockEditor.blockType.paragraph",
37230
- "heading-1": "richBlockEditor.blockType.heading1",
37231
- "heading-2": "richBlockEditor.blockType.heading2",
37232
- "heading-3": "richBlockEditor.blockType.heading3",
37233
- "bullet-list": "richBlockEditor.blockType.bulletList",
37234
- "numbered-list": "richBlockEditor.blockType.numberedList",
37235
- quote: "richBlockEditor.blockType.quote",
37236
- code: "richBlockEditor.blockType.code",
37237
- divider: "richBlockEditor.blockType.divider",
37238
- image: "richBlockEditor.blockType.image"
37239
- };
37240
- CHANGEABLE_TYPES = [
37241
- "paragraph",
37242
- "heading-1",
37243
- "heading-2",
37244
- "heading-3",
37245
- "bullet-list",
37246
- "numbered-list",
37247
- "quote",
37248
- "code"
37249
- ];
37250
- _idSeq = 0;
37251
- BLOCK_TYPES = /* @__PURE__ */ new Set([
37252
- "paragraph",
37253
- "heading-1",
37254
- "heading-2",
37255
- "heading-3",
37256
- "bullet-list",
37257
- "numbered-list",
37258
- "quote",
37259
- "code",
37260
- "divider",
37261
- "image"
37262
- ]);
37263
- RichBlockEditor = ({
37264
- initialBlocks,
37265
- onChange,
37266
- changeEvent,
37267
- readOnly = false,
37268
- placeholder,
37269
- enableBlocks = true,
37270
- showToolbar = true,
37271
- className
37272
- }) => {
37273
- const { t } = useTranslate();
37274
- const [blocks, setBlocks] = useState(
37275
- () => normalizeBlocks(initialBlocks)
37276
- );
37277
- const onChangeRef = useRef(onChange);
37278
- useEffect(() => {
37279
- onChangeRef.current = onChange;
37280
- }, [onChange]);
37281
- const eventBus = useEventBus();
37282
- const changeEventRef = useRef(changeEvent);
37283
- useEffect(() => {
37284
- changeEventRef.current = changeEvent;
37285
- }, [changeEvent]);
37286
- const commit = useCallback((next) => {
37287
- setBlocks(next);
37288
- onChangeRef.current?.(next);
37289
- const evt = changeEventRef.current;
37290
- if (evt) eventBus.emit(`UI:${evt}`, { blocks: next });
37291
- }, [eventBus]);
37292
- const handleAppend = useCallback(
37293
- (type) => {
37294
- if (readOnly) return;
37295
- commit([...blocks, createBlock(type)]);
37296
- },
37297
- [blocks, commit, readOnly]
37298
- );
37299
- const handleUpdate = useCallback(
37300
- (id, updater) => {
37301
- commit(replaceBlock(blocks, id, updater));
37302
- },
37303
- [blocks, commit]
37304
- );
37305
- const handleDelete = useCallback(
37306
- (id) => {
37307
- const next = removeBlock(blocks, id);
37308
- commit(next.length > 0 ? next : [createBlock("paragraph")]);
37309
- },
37310
- [blocks, commit]
37311
- );
37312
- const handleDuplicate = useCallback(
37313
- (id) => {
37314
- const target = blocks.find((b) => b.id === id);
37315
- if (!target) return;
37316
- commit(insertAfter(blocks, id, duplicateBlock(target)));
37317
- },
37318
- [blocks, commit]
37236
+ init_Input();
37237
+ init_Menu();
37238
+ init_RichTextEditor();
37239
+ }
37240
+ });
37241
+ function renderIconInput5(icon, props) {
37242
+ return typeof icon === "string" ? /* @__PURE__ */ jsx(Icon, { name: icon, ...props }) : /* @__PURE__ */ jsx(Icon, { icon, ...props });
37243
+ }
37244
+ function fieldName(field) {
37245
+ return field.name ?? field.key ?? "";
37246
+ }
37247
+ function relationLabel(value) {
37248
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
37249
+ for (const key of ["name", "title", "label"]) {
37250
+ const candidate = value[key];
37251
+ if (typeof candidate === "string" && candidate !== "") return candidate;
37252
+ }
37253
+ const id = value.id;
37254
+ return id !== void 0 && id !== null ? String(id) : null;
37255
+ }
37256
+ function fieldKind(field, value) {
37257
+ if (field.kind) return field.kind;
37258
+ if (field.options && field.options.length > 0) return "select";
37259
+ if (typeof value === "boolean" || field.format === "boolean") return "boolean";
37260
+ if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
37261
+ return "text";
37262
+ }
37263
+ function DocumentDetails({
37264
+ entity,
37265
+ fields,
37266
+ metaCommitEvent,
37267
+ title,
37268
+ className
37269
+ }) {
37270
+ const eventBus = useEventBus();
37271
+ const { t } = useTranslate();
37272
+ const [fieldDraft, setFieldDraft] = useState(null);
37273
+ const recordId = entity?.id !== void 0 && entity?.id !== null ? String(entity.id) : "";
37274
+ const fieldDefs = (fields ?? []).filter((f3) => fieldName(f3) !== "");
37275
+ if (fieldDefs.length === 0) return null;
37276
+ const commitField = (name, next, current) => {
37277
+ setFieldDraft(null);
37278
+ if (!metaCommitEvent || next === current) return;
37279
+ eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
37280
+ };
37281
+ const renderValue = (field) => {
37282
+ const name = fieldName(field);
37283
+ const raw = getNestedValue(entity ?? {}, name);
37284
+ const kind = fieldKind(field, raw);
37285
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
37286
+ if (kind === "boolean") {
37287
+ return /* @__PURE__ */ jsx(
37288
+ Switch,
37289
+ {
37290
+ checked: Boolean(raw),
37291
+ disabled: !metaCommitEvent,
37292
+ "aria-label": label,
37293
+ onChange: (checked) => commitField(name, checked, Boolean(raw))
37294
+ }
37319
37295
  );
37320
- const handleChangeType = useCallback(
37321
- (id, type) => {
37322
- commit(
37323
- replaceBlock(blocks, id, (b) => changeBlockType(b, type))
37324
- );
37325
- },
37326
- [blocks, commit]
37296
+ }
37297
+ if (kind === "select") {
37298
+ return /* @__PURE__ */ jsx(
37299
+ Select,
37300
+ {
37301
+ value: raw !== void 0 && raw !== null ? String(raw) : "",
37302
+ disabled: !metaCommitEvent,
37303
+ "aria-label": label,
37304
+ className: "h-8 w-full",
37305
+ options: (field.options ?? []).map((opt) => ({ value: opt, label: humanizeEnumValue(opt) })),
37306
+ onChange: (e) => commitField(name, e.target.value, raw)
37307
+ }
37327
37308
  );
37328
- return /* @__PURE__ */ jsxs(
37329
- Card,
37309
+ }
37310
+ if (kind === "readonly" || !metaCommitEvent) {
37311
+ if (Array.isArray(raw)) {
37312
+ if (raw.length === 0) {
37313
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", color: "secondary", children: "\u2014" });
37314
+ }
37315
+ return /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsx(Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
37316
+ }
37317
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
37318
+ return /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown2 });
37319
+ }
37320
+ if (fieldDraft?.name === name) {
37321
+ return /* @__PURE__ */ jsx(
37322
+ Input,
37330
37323
  {
37331
- variant: "bordered",
37332
- padding: "none",
37333
- className: cn("flex flex-col text-card-foreground", className),
37334
- children: [
37335
- enableBlocks && showToolbar && !readOnly && /* @__PURE__ */ jsx(
37336
- Box,
37337
- {
37338
- role: "toolbar",
37339
- "aria-label": t("richBlockEditor.editorToolbar"),
37340
- className: cn(
37341
- "flex flex-wrap items-center gap-1",
37342
- "border-b border-border bg-muted/30 px-2 py-2"
37343
- ),
37344
- children: TOOLBAR_ENTRIES.map((entry) => {
37345
- const Icon2 = entry.icon;
37346
- const entryLabel = t(entry.labelKey);
37347
- return /* @__PURE__ */ jsxs(
37348
- Button,
37349
- {
37350
- type: "button",
37351
- variant: "ghost",
37352
- size: "sm",
37353
- "aria-label": t("richBlockEditor.insertEntry", { label: entryLabel }),
37354
- title: entryLabel,
37355
- onClick: () => handleAppend(entry.type),
37356
- children: [
37357
- /* @__PURE__ */ jsx(Icon2, { size: 14 }),
37358
- /* @__PURE__ */ jsx(Typography, { as: "span", variant: "caption", className: "ml-1 hidden text-xs sm:inline", children: entryLabel })
37359
- ]
37360
- },
37361
- entry.type
37362
- );
37363
- })
37364
- }
37365
- ),
37366
- /* @__PURE__ */ jsx(Box, { className: "flex flex-col gap-1 px-3 py-3", children: blocks.map((block) => /* @__PURE__ */ jsx(
37367
- BlockRow,
37368
- {
37369
- block,
37370
- readOnly,
37371
- showAffordances: enableBlocks,
37372
- placeholder,
37373
- onUpdate: (updater) => handleUpdate(block.id, updater),
37374
- onDelete: () => handleDelete(block.id),
37375
- onDuplicate: () => handleDuplicate(block.id),
37376
- onChangeType: (type) => handleChangeType(block.id, type)
37377
- },
37378
- block.id
37379
- )) })
37380
- ]
37324
+ value: fieldDraft.value,
37325
+ autoFocus: true,
37326
+ "aria-label": label,
37327
+ className: "h-8 w-full",
37328
+ onChange: (e) => setFieldDraft({ name, value: e.target.value }),
37329
+ onBlur: () => commitField(name, fieldDraft.value.trim(), raw),
37330
+ onKeyDown: (e) => {
37331
+ if (e.key === "Enter") {
37332
+ e.preventDefault();
37333
+ commitField(name, fieldDraft.value.trim(), raw);
37334
+ } else if (e.key === "Escape") {
37335
+ e.preventDefault();
37336
+ setFieldDraft(null);
37337
+ }
37338
+ },
37339
+ "data-testid": `document-property-input-${name}`
37381
37340
  }
37382
37341
  );
37383
- };
37384
- RichBlockEditor.displayName = "RichBlockEditor";
37342
+ }
37343
+ const shown = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
37344
+ return /* @__PURE__ */ jsx(
37345
+ Box,
37346
+ {
37347
+ role: "button",
37348
+ tabIndex: 0,
37349
+ className: cn(
37350
+ "cursor-text rounded px-1 -mx-1 transition-colors hover:bg-muted/40",
37351
+ (raw === void 0 || raw === null || raw === "") && "text-muted-foreground"
37352
+ ),
37353
+ onClick: () => setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" }),
37354
+ onKeyDown: (e) => {
37355
+ if (e.key === "Enter" || e.key === " ") {
37356
+ e.preventDefault();
37357
+ setFieldDraft({ name, value: raw !== void 0 && raw !== null ? String(raw) : "" });
37358
+ }
37359
+ },
37360
+ "data-testid": `document-property-${name}`,
37361
+ children: /* @__PURE__ */ jsx(Typography, { variant: "small", className: "break-words", children: shown })
37362
+ }
37363
+ );
37364
+ };
37365
+ return /* @__PURE__ */ jsx(Card, { variant: "bordered", className: cn("w-full", className), children: /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: "p-4", children: [
37366
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", weight: "medium", className: "uppercase tracking-wide", children: title ?? (t("documentDetails.title") || "Details") }),
37367
+ /* @__PURE__ */ jsx(VStack, { gap: "sm", children: fieldDefs.map((field) => {
37368
+ const name = fieldName(field);
37369
+ const label = field.label ?? field.header ?? humanizeFieldName(name);
37370
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", children: [
37371
+ /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center", children: [
37372
+ field.icon && renderIconInput5(field.icon, { size: "xs", className: "text-muted-foreground" }),
37373
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: label })
37374
+ ] }),
37375
+ /* @__PURE__ */ jsx(Box, { className: "min-w-0", children: renderValue(field) })
37376
+ ] }, name);
37377
+ }) })
37378
+ ] }) });
37379
+ }
37380
+ var init_DocumentDetails = __esm({
37381
+ "components/core/molecules/DocumentDetails.tsx"() {
37382
+ "use client";
37383
+ init_cn();
37384
+ init_format();
37385
+ init_getNestedValue();
37386
+ init_useEventBus();
37387
+ init_Box();
37388
+ init_Stack();
37389
+ init_Typography();
37390
+ init_Badge();
37391
+ init_Card();
37392
+ init_Icon();
37393
+ init_Input();
37394
+ init_Select();
37395
+ init_Switch();
37385
37396
  }
37386
37397
  });
37387
37398
  function collectInitiallyCollapsed(nodes, acc) {
@@ -42330,8 +42341,8 @@ var init_DataTable = __esm({
42330
42341
  DataTable.displayName = "DataTable";
42331
42342
  }
42332
42343
  });
42333
- function getBadgeVariant(fieldName, value) {
42334
- const name = fieldName.toLowerCase();
42344
+ function getBadgeVariant(fieldName2, value) {
42345
+ const name = fieldName2.toLowerCase();
42335
42346
  const val = String(value).toLowerCase();
42336
42347
  if (name.includes("status")) {
42337
42348
  if (val.includes("complete") || val.includes("done") || val.includes("active"))
@@ -42347,12 +42358,12 @@ function getBadgeVariant(fieldName, value) {
42347
42358
  }
42348
42359
  return "default";
42349
42360
  }
42350
- function formatFieldValue2(value, fieldName) {
42361
+ function formatFieldValue2(value, fieldName2) {
42351
42362
  if (typeof value === "number") {
42352
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
42363
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
42353
42364
  return `${value}%`;
42354
42365
  }
42355
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
42366
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
42356
42367
  return `$${value.toLocaleString()}`;
42357
42368
  }
42358
42369
  return value.toLocaleString();
@@ -42362,7 +42373,7 @@ function formatFieldValue2(value, fieldName) {
42362
42373
  }
42363
42374
  return String(value);
42364
42375
  }
42365
- function renderRichFieldValue(value, fieldName, fieldType, meta) {
42376
+ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
42366
42377
  if (value === void 0 || value === null) return "\u2014";
42367
42378
  const str = String(value);
42368
42379
  switch (fieldType) {
@@ -42373,7 +42384,7 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
42373
42384
  "img",
42374
42385
  {
42375
42386
  src: str,
42376
- alt: formatFieldLabel(fieldName),
42387
+ alt: formatFieldLabel(fieldName2),
42377
42388
  className: "max-w-full max-h-64 rounded-md object-contain",
42378
42389
  loading: "lazy"
42379
42390
  }
@@ -42508,9 +42519,9 @@ function renderRichFieldValue(value, fieldName, fieldType, meta) {
42508
42519
  return /* @__PURE__ */ jsx("a", { href: `tel:${str}`, className: "text-primary hover:underline", children: str });
42509
42520
  default:
42510
42521
  if (meta?.values && meta.values.length > 0 && meta.values.includes(str)) {
42511
- return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName, str), children: humanizeEnumValue(str) });
42522
+ return /* @__PURE__ */ jsx(Badge, { variant: getBadgeVariant(fieldName2, str), children: humanizeEnumValue(str) });
42512
42523
  }
42513
- return formatFieldValue2(value, fieldName);
42524
+ return formatFieldValue2(value, fieldName2);
42514
42525
  }
42515
42526
  }
42516
42527
  function normalizeFieldDefs(fields) {
@@ -43353,7 +43364,21 @@ var init_Form = __esm({
43353
43364
  const normalizedInitialData = React87__default.useMemo(() => {
43354
43365
  const entityRowAsInitial = isPlainEntityRow(entity) ? entity : void 0;
43355
43366
  const callerInitial = initialData !== null && typeof initialData === "object" && !Array.isArray(initialData) ? initialData : {};
43356
- return entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
43367
+ const merged = entityRowAsInitial !== void 0 ? { ...entityRowAsInitial, ...callerInitial } : callerInitial;
43368
+ const toId = (value) => {
43369
+ if (value === null || value === void 0 || typeof value !== "object" || value instanceof Date) return value;
43370
+ if (Array.isArray(value)) {
43371
+ return value.map(
43372
+ (item) => item !== null && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && item.id !== void 0 && item.id !== null ? String(item.id) : item
43373
+ );
43374
+ }
43375
+ return value.id !== void 0 && value.id !== null ? String(value.id) : value;
43376
+ };
43377
+ const normalized = {};
43378
+ for (const [key, value] of Object.entries(merged)) {
43379
+ normalized[key] = key === "id" ? value : toId(value);
43380
+ }
43381
+ return normalized;
43357
43382
  }, [entity, initialData]);
43358
43383
  const entityDerivedFields = React87__default.useMemo(() => {
43359
43384
  if (fields && fields.length > 0) return void 0;
@@ -43482,8 +43507,8 @@ var init_Form = __esm({
43482
43507
  checkViolations(name, newFormData);
43483
43508
  };
43484
43509
  const isFieldVisible = React87__default.useCallback(
43485
- (fieldName) => {
43486
- const condition = conditionalFields[fieldName];
43510
+ (fieldName2) => {
43511
+ const condition = conditionalFields[fieldName2];
43487
43512
  if (!condition) return true;
43488
43513
  return Boolean(evaluateFormExpression(condition, evalContext));
43489
43514
  },
@@ -43529,9 +43554,9 @@ var init_Form = __esm({
43529
43554
  };
43530
43555
  const handleInvalid = (e) => {
43531
43556
  const target = e.target;
43532
- const fieldName = target.getAttribute("data-field-name") ?? target.name ?? "";
43557
+ const fieldName2 = target.getAttribute("data-field-name") ?? target.name ?? "";
43533
43558
  const fieldMessage = target.validationMessage || "Invalid value";
43534
- debug("forms", "invalid", { mode: formMode, fieldName, fieldMessage });
43559
+ debug("forms", "invalid", { mode: formMode, fieldName: fieldName2, fieldMessage });
43535
43560
  queueMicrotask(() => {
43536
43561
  const form = formRef.current;
43537
43562
  if (!form) return;
@@ -43567,22 +43592,22 @@ var init_Form = __esm({
43567
43592
  };
43568
43593
  const renderField = React87__default.useCallback(
43569
43594
  (field) => {
43570
- const fieldName = field.name || field.field;
43571
- if (!fieldName) return null;
43572
- if (!isFieldVisible(fieldName)) {
43595
+ const fieldName2 = field.name || field.field;
43596
+ if (!fieldName2) return null;
43597
+ if (!isFieldVisible(fieldName2)) {
43573
43598
  return null;
43574
43599
  }
43575
43600
  const inputType = determineInputType(field);
43576
- const label = field.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, " $1");
43577
- const currentValue = formData[fieldName] ?? field.defaultValue ?? "";
43578
- return /* @__PURE__ */ jsxs(VStack, { gap: "xs", "data-field": fieldName, children: [
43601
+ const label = field.label || fieldName2.charAt(0).toUpperCase() + fieldName2.slice(1).replace(/([A-Z])/g, " $1");
43602
+ const currentValue = formData[fieldName2] ?? field.defaultValue ?? "";
43603
+ return /* @__PURE__ */ jsxs(VStack, { gap: "xs", "data-field": fieldName2, children: [
43579
43604
  inputType !== "checkbox" && /* @__PURE__ */ jsxs(Typography, { as: "label", variant: "label", weight: "bold", children: [
43580
43605
  label,
43581
43606
  field.required && /* @__PURE__ */ jsx(Typography, { as: "span", color: "error", className: "ml-1", children: "*" })
43582
43607
  ] }),
43583
- renderFieldInput(field, fieldName, inputType, currentValue, label),
43608
+ renderFieldInput(field, fieldName2, inputType, currentValue, label),
43584
43609
  field.hint && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "muted", children: field.hint })
43585
- ] }, fieldName);
43610
+ ] }, fieldName2);
43586
43611
  },
43587
43612
  [formData, isFieldVisible, relationsData, relationsLoading, isLoading]
43588
43613
  );
@@ -43611,8 +43636,8 @@ var init_Form = __esm({
43611
43636
  }
43612
43637
  return field;
43613
43638
  }).map((field) => {
43614
- const fieldName = field.name || field.field;
43615
- const override = fieldOverrides?.find((o) => o.name === fieldName);
43639
+ const fieldName2 = field.name || field.field;
43640
+ const override = fieldOverrides?.find((o) => o.name === fieldName2);
43616
43641
  if (!override) return field;
43617
43642
  return {
43618
43643
  ...field,
@@ -43668,11 +43693,11 @@ var init_Form = __esm({
43668
43693
  ] }, section.id);
43669
43694
  }).filter(Boolean);
43670
43695
  }, [sections, isSectionVisible, collapsedSections, renderField, gap]);
43671
- function renderFieldInput(field, fieldName, inputType, currentValue, label) {
43696
+ function renderFieldInput(field, fieldName2, inputType, currentValue, label) {
43672
43697
  const commonProps = {
43673
- id: fieldName,
43674
- name: fieldName,
43675
- "data-field-name": fieldName,
43698
+ id: fieldName2,
43699
+ name: fieldName2,
43700
+ "data-field-name": fieldName2,
43676
43701
  required: field.required,
43677
43702
  disabled: isLoading,
43678
43703
  placeholder: field.placeholder,
@@ -43686,7 +43711,7 @@ var init_Form = __esm({
43686
43711
  ...commonProps,
43687
43712
  label: label + (field.required ? " *" : ""),
43688
43713
  checked: Boolean(currentValue),
43689
- onChange: (e) => handleChange(fieldName, e.target.checked)
43714
+ onChange: (e) => handleChange(fieldName2, e.target.checked)
43690
43715
  }
43691
43716
  );
43692
43717
  case "textarea":
@@ -43695,7 +43720,7 @@ var init_Form = __esm({
43695
43720
  {
43696
43721
  ...commonProps,
43697
43722
  value: String(currentValue),
43698
- onChange: (e) => handleChange(fieldName, e.target.value),
43723
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43699
43724
  minLength: field.min,
43700
43725
  maxLength: field.max
43701
43726
  }
@@ -43708,14 +43733,14 @@ var init_Form = __esm({
43708
43733
  ...commonProps,
43709
43734
  options,
43710
43735
  value: String(currentValue),
43711
- onValueChange: (v) => handleChange(fieldName, v),
43736
+ onValueChange: (v) => handleChange(fieldName2, v),
43712
43737
  placeholder: field.placeholder || `Select ${label}...`
43713
43738
  }
43714
43739
  );
43715
43740
  }
43716
43741
  case "relation": {
43717
- const relationOptions = relationsData[fieldName] || [];
43718
- const relationLoading = relationsLoading[fieldName] || false;
43742
+ const relationOptions = relationsData[fieldName2] || [];
43743
+ const relationLoading = relationsLoading[fieldName2] || false;
43719
43744
  if (field.relation?.cardinality !== void 0 && MANY_CARDINALITIES.includes(field.relation.cardinality)) {
43720
43745
  const selectedValues = Array.isArray(currentValue) ? currentValue.map((v) => String(v)) : [];
43721
43746
  return /* @__PURE__ */ jsx(
@@ -43727,7 +43752,7 @@ var init_Form = __esm({
43727
43752
  clearable: true,
43728
43753
  options: [...relationOptions],
43729
43754
  value: selectedValues,
43730
- onValueChange: (value) => handleChange(fieldName, Array.isArray(value) ? value : [value]),
43755
+ onValueChange: (value) => handleChange(fieldName2, Array.isArray(value) ? value : [value]),
43731
43756
  placeholder: field.placeholder || `Select ${label}...`
43732
43757
  }
43733
43758
  );
@@ -43737,7 +43762,7 @@ var init_Form = __esm({
43737
43762
  {
43738
43763
  ...commonProps,
43739
43764
  value: currentValue ? String(currentValue) : void 0,
43740
- onChange: (value) => handleChange(fieldName, value),
43765
+ onChange: (value) => handleChange(fieldName2, value),
43741
43766
  options: relationOptions,
43742
43767
  isLoading: relationLoading,
43743
43768
  placeholder: field.placeholder || `Select ${label}...`,
@@ -43754,7 +43779,7 @@ var init_Form = __esm({
43754
43779
  placeholder: field.placeholder,
43755
43780
  disabled: isLoading,
43756
43781
  value: arrayValue,
43757
- onChange: (next) => handleChange(fieldName, [...next])
43782
+ onChange: (next) => handleChange(fieldName2, [...next])
43758
43783
  }
43759
43784
  );
43760
43785
  }
@@ -43766,7 +43791,7 @@ var init_Form = __esm({
43766
43791
  type: "number",
43767
43792
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
43768
43793
  onChange: (e) => handleChange(
43769
- fieldName,
43794
+ fieldName2,
43770
43795
  e.target.value ? Number(e.target.value) : void 0
43771
43796
  ),
43772
43797
  min: field.min,
@@ -43783,7 +43808,7 @@ var init_Form = __esm({
43783
43808
  icon: DollarSign,
43784
43809
  value: currentValue !== void 0 && currentValue !== "" ? String(currentValue) : "",
43785
43810
  onChange: (e) => handleChange(
43786
- fieldName,
43811
+ fieldName2,
43787
43812
  e.target.value ? Number(e.target.value) : void 0
43788
43813
  ),
43789
43814
  min: field.min,
@@ -43801,7 +43826,7 @@ var init_Form = __esm({
43801
43826
  const f3 = files[0];
43802
43827
  if (!f3) return;
43803
43828
  const reader = new FileReader();
43804
- reader.onload = () => handleChange(fieldName, {
43829
+ reader.onload = () => handleChange(fieldName2, {
43805
43830
  name: f3.name,
43806
43831
  mimeType: f3.type,
43807
43832
  sizeBytes: f3.size,
@@ -43818,7 +43843,7 @@ var init_Form = __esm({
43818
43843
  ...commonProps,
43819
43844
  type: "date",
43820
43845
  value: formatDateValue(currentValue),
43821
- onChange: (e) => handleChange(fieldName, e.target.value)
43846
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43822
43847
  }
43823
43848
  );
43824
43849
  case "datetime-local":
@@ -43828,7 +43853,7 @@ var init_Form = __esm({
43828
43853
  ...commonProps,
43829
43854
  type: "datetime-local",
43830
43855
  value: formatDateTimeValue(currentValue),
43831
- onChange: (e) => handleChange(fieldName, e.target.value)
43856
+ onChange: (e) => handleChange(fieldName2, e.target.value)
43832
43857
  }
43833
43858
  );
43834
43859
  case "email":
@@ -43838,7 +43863,7 @@ var init_Form = __esm({
43838
43863
  ...commonProps,
43839
43864
  type: "email",
43840
43865
  value: String(currentValue),
43841
- onChange: (e) => handleChange(fieldName, e.target.value),
43866
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43842
43867
  minLength: field.min,
43843
43868
  maxLength: field.max
43844
43869
  }
@@ -43850,7 +43875,7 @@ var init_Form = __esm({
43850
43875
  ...commonProps,
43851
43876
  type: "url",
43852
43877
  value: String(currentValue),
43853
- onChange: (e) => handleChange(fieldName, e.target.value),
43878
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43854
43879
  minLength: field.min,
43855
43880
  maxLength: field.max
43856
43881
  }
@@ -43862,7 +43887,7 @@ var init_Form = __esm({
43862
43887
  ...commonProps,
43863
43888
  type: "password",
43864
43889
  value: String(currentValue),
43865
- onChange: (e) => handleChange(fieldName, e.target.value),
43890
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43866
43891
  minLength: field.min,
43867
43892
  maxLength: field.max
43868
43893
  }
@@ -43875,7 +43900,7 @@ var init_Form = __esm({
43875
43900
  ...commonProps,
43876
43901
  type: "text",
43877
43902
  value: String(currentValue),
43878
- onChange: (e) => handleChange(fieldName, e.target.value),
43903
+ onChange: (e) => handleChange(fieldName2, e.target.value),
43879
43904
  minLength: field.min,
43880
43905
  maxLength: field.max
43881
43906
  }
@@ -44232,7 +44257,7 @@ function entityFieldsFromListItem(item) {
44232
44257
  }
44233
44258
  return result;
44234
44259
  }
44235
- function getStatusStyle(fieldName, value) {
44260
+ function getStatusStyle(fieldName2, value) {
44236
44261
  const val = String(value).toLowerCase();
44237
44262
  if (val.includes("complete") || val.includes("done"))
44238
44263
  return STATUS_STYLES.complete;
@@ -44248,12 +44273,12 @@ function getStatusStyle(fieldName, value) {
44248
44273
  if (val.includes("low")) return STATUS_STYLES.low;
44249
44274
  return STATUS_STYLES.default;
44250
44275
  }
44251
- function formatValue3(value, fieldName) {
44276
+ function formatValue3(value, fieldName2) {
44252
44277
  if (typeof value === "number") {
44253
- if (fieldName.toLowerCase().includes("progress") || fieldName.toLowerCase().includes("percent")) {
44278
+ if (fieldName2.toLowerCase().includes("progress") || fieldName2.toLowerCase().includes("percent")) {
44254
44279
  return `${value}%`;
44255
44280
  }
44256
- if (fieldName.toLowerCase().includes("budget") || fieldName.toLowerCase().includes("cost")) {
44281
+ if (fieldName2.toLowerCase().includes("budget") || fieldName2.toLowerCase().includes("cost")) {
44257
44282
  return new Intl.NumberFormat("en-US", {
44258
44283
  style: "currency",
44259
44284
  currency: "USD",
@@ -44276,8 +44301,8 @@ function formatValue3(value, fieldName) {
44276
44301
  }
44277
44302
  return String(value);
44278
44303
  }
44279
- function formatFieldLabel2(fieldName) {
44280
- return humanizeFieldName(fieldName).replace(/\sId$/, "").trim();
44304
+ function formatFieldLabel2(fieldName2) {
44305
+ return humanizeFieldName(fieldName2).replace(/\sId$/, "").trim();
44281
44306
  }
44282
44307
  var STATUS_STYLES, StatusBadge, ProgressIndicator, List3;
44283
44308
  var init_List = __esm({
@@ -44345,9 +44370,9 @@ var init_List = __esm({
44345
44370
  };
44346
44371
  StatusBadge = ({
44347
44372
  value,
44348
- fieldName
44373
+ fieldName: fieldName2
44349
44374
  }) => {
44350
- const style = getStatusStyle(fieldName, value);
44375
+ const style = getStatusStyle(fieldName2, value);
44351
44376
  return /* @__PURE__ */ jsxs(
44352
44377
  Typography,
44353
44378
  {
@@ -47260,8 +47285,8 @@ var init_StatCard = __esm({
47260
47285
  return items.length;
47261
47286
  }
47262
47287
  if (field.includes(":")) {
47263
- const [fieldName, fieldValue] = field.split(":");
47264
- return items.filter((item) => item[fieldName] === fieldValue).length;
47288
+ const [fieldName2, fieldValue] = field.split(":");
47289
+ return items.filter((item) => item[fieldName2] === fieldValue).length;
47265
47290
  }
47266
47291
  const fieldExistsOnItems = items.some((item) => field in item);
47267
47292
  if (fieldExistsOnItems) {
@@ -48781,6 +48806,8 @@ var init_component_registry_generated = __esm({
48781
48806
  init_DocSearch();
48782
48807
  init_DocSidebar();
48783
48808
  init_DocTOC();
48809
+ init_DocumentDetails();
48810
+ init_DocumentPanel();
48784
48811
  init_DocumentViewer();
48785
48812
  init_DrawFxLayer();
48786
48813
  init_DrawGroup();
@@ -48890,7 +48917,7 @@ var init_component_registry_generated = __esm({
48890
48917
  init_RelationSelect();
48891
48918
  init_RepeatableFormSection();
48892
48919
  init_ReplyTree();
48893
- init_RichBlockEditor();
48920
+ init_RichTextEditor();
48894
48921
  init_RuntimeDebugger2();
48895
48922
  init_ScaledDiagram();
48896
48923
  init_ScoreDisplay();
@@ -49053,6 +49080,8 @@ var init_component_registry_generated = __esm({
49053
49080
  "DocSearch": DocSearch,
49054
49081
  "DocSidebar": DocSidebar,
49055
49082
  "DocTOC": DocTOC,
49083
+ "DocumentDetails": DocumentDetails,
49084
+ "DocumentPanel": DocumentPanel,
49056
49085
  "DocumentViewer": DocumentViewer,
49057
49086
  "DrawFxLayer": DrawFxLayer,
49058
49087
  "DrawGroup": DrawGroup,
@@ -49164,7 +49193,7 @@ var init_component_registry_generated = __esm({
49164
49193
  "RelationSelect": RelationSelect,
49165
49194
  "RepeatableFormSection": RepeatableFormSection,
49166
49195
  "ReplyTree": ReplyTree,
49167
- "RichBlockEditor": RichBlockEditor,
49196
+ "RichTextEditor": RichTextEditor,
49168
49197
  "RuntimeDebugger": RuntimeDebugger,
49169
49198
  "ScaledDiagram": ScaledDiagram,
49170
49199
  "ScoreDisplay": ScoreDisplay,
@@ -49318,10 +49347,10 @@ function enrichFormFields(fields, entityDef) {
49318
49347
  }
49319
49348
  if (field && typeof field === "object" && !Array.isArray(field) && !React87__default.isValidElement(field) && !(field instanceof Date)) {
49320
49349
  const obj = field;
49321
- const fieldName = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
49322
- if (!fieldName) return field;
49350
+ const fieldName2 = typeof obj.name === "string" ? obj.name : typeof obj.field === "string" ? obj.field : void 0;
49351
+ if (!fieldName2) return field;
49323
49352
  if (obj.type || obj.inputType) return field;
49324
- const entityField = fieldMap.get(fieldName);
49353
+ const entityField = fieldMap.get(fieldName2);
49325
49354
  if (!entityField) return field;
49326
49355
  const enriched = { ...obj, type: entityField.type };
49327
49356
  if (entityField.required && !("required" in obj)) {
@@ -49371,9 +49400,9 @@ function enrichDetailFields(fields, entityDef) {
49371
49400
  }
49372
49401
  if (field && typeof field === "object" && !Array.isArray(field) && !React87__default.isValidElement(field) && !(field instanceof Date)) {
49373
49402
  const obj = field;
49374
- const fieldName = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
49375
- if (!fieldName || obj.type) return field;
49376
- const meta = metaFor(fieldName);
49403
+ const fieldName2 = typeof obj.key === "string" ? obj.key : typeof obj.name === "string" ? obj.name : void 0;
49404
+ if (!fieldName2 || obj.type) return field;
49405
+ const meta = metaFor(fieldName2);
49377
49406
  return meta ? { ...obj, ...meta } : field;
49378
49407
  }
49379
49408
  return field;
@@ -50212,9 +50241,9 @@ function UISlotRenderer({
50212
50241
  "ui-slot-renderer relative min-h-full",
50213
50242
  className
50214
50243
  ), children: [
50215
- /* @__PURE__ */ jsxs(Box, { className: "flex min-h-full", children: [
50216
- /* @__PURE__ */ jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 shrink-0" }),
50217
- /* @__PURE__ */ jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-h-[200px]" })
50244
+ /* @__PURE__ */ jsxs(Box, { className: "flex min-h-full flex-col lg:flex-row", children: [
50245
+ /* @__PURE__ */ jsx(UISlotComponent, { slot: "sidebar", className: "ui-slot-sidebar min-w-0 lg:shrink-0" }),
50246
+ /* @__PURE__ */ jsx(UISlotComponent, { slot: "main", className: "ui-slot-main flex-1 min-w-0 min-h-[200px]" })
50218
50247
  ] }),
50219
50248
  /* @__PURE__ */ jsx(UISlotComponent, { slot: "modal", portal: true }),
50220
50249
  /* @__PURE__ */ jsx(UISlotComponent, { slot: "drawer", portal: true }),