@almadar/ui 5.162.0 → 5.163.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.
@@ -34180,6 +34180,33 @@ var init_Lightbox = __esm({
34180
34180
  exports.Lightbox.displayName = "Lightbox";
34181
34181
  }
34182
34182
  });
34183
+
34184
+ // lib/relationLabel.ts
34185
+ function relationLabel(value) {
34186
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date)
34187
+ return null;
34188
+ for (const key of ["name", "title", "label"]) {
34189
+ const candidate = value[key];
34190
+ if (typeof candidate === "string" && candidate !== "") return candidate;
34191
+ }
34192
+ const id = value.id;
34193
+ return id !== void 0 && id !== null ? String(id) : null;
34194
+ }
34195
+ function relationDisplayLabels(value, options) {
34196
+ if (value === void 0 || value === null || value === "") return [];
34197
+ if (Array.isArray(value)) {
34198
+ return value.flatMap((item) => relationDisplayLabels(item, options));
34199
+ }
34200
+ const hydrated = relationLabel(value);
34201
+ if (hydrated !== null) return [hydrated];
34202
+ const raw = String(value);
34203
+ const match = options?.find((opt) => opt.value === raw);
34204
+ return [match ? match.label : raw];
34205
+ }
34206
+ var init_relationLabel = __esm({
34207
+ "lib/relationLabel.ts"() {
34208
+ }
34209
+ });
34183
34210
  function renderIconInput3(icon, props) {
34184
34211
  return typeof icon === "string" ? /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: icon, ...props }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { icon, ...props });
34185
34212
  }
@@ -34497,6 +34524,7 @@ var init_TableView = __esm({
34497
34524
  "use client";
34498
34525
  init_cn();
34499
34526
  init_format();
34527
+ init_relationLabel();
34500
34528
  init_getNestedValue();
34501
34529
  init_useEventBus();
34502
34530
  init_Box();
@@ -34510,7 +34538,13 @@ var init_TableView = __esm({
34510
34538
  init_Menu();
34511
34539
  init_useDataDnd();
34512
34540
  tableViewLog = logger.createLogger("almadar:ui:table-view");
34513
- formatCell = (value, format) => formatValue(value, format);
34541
+ formatCell = (value, format) => {
34542
+ if (value !== null && value !== void 0 && typeof value === "object" && !(value instanceof Date)) {
34543
+ const labels = relationDisplayLabels(value);
34544
+ if (labels.length > 0) return labels.join(", ");
34545
+ }
34546
+ return formatValue(value, format);
34547
+ };
34514
34548
  MAX_MEASURED_COL_CH = 32;
34515
34549
  BADGE_CHROME_CH = 4;
34516
34550
  alignClass = {
@@ -38125,6 +38159,13 @@ var init_RichTextEditor = __esm({
38125
38159
  document.execCommand("createLink", false, safe);
38126
38160
  afterEdit();
38127
38161
  }, [afterEdit, t, toolbar.link]);
38162
+ const execImage = React77.useCallback(() => {
38163
+ const url = window.prompt(t("richTextEditor.imagePrompt"));
38164
+ if (!url) return;
38165
+ const safe = safeUrl(url, ["http://", "https://", "data:image/"]) ?? `https://${url}`;
38166
+ document.execCommand("insertImage", false, safe);
38167
+ afterEdit();
38168
+ }, [afterEdit, t]);
38128
38169
  const execRule = React77.useCallback(() => {
38129
38170
  document.execCommand("insertHorizontalRule", false);
38130
38171
  afterEdit();
@@ -38162,6 +38203,7 @@ var init_RichTextEditor = __esm({
38162
38203
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Quote, label: t("richTextEditor.quote"), active: toolbar.block === "blockquote", onExec: () => execBlock("blockquote") }),
38163
38204
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Code, label: t("richTextEditor.code"), active: toolbar.block === "pre", onExec: () => execBlock("pre") }),
38164
38205
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Link, label: toolbar.link ? t("richTextEditor.removeLink") : t("richTextEditor.link"), active: toolbar.link, onExec: execLink }),
38206
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Image, label: t("richTextEditor.image"), onExec: execImage }),
38165
38207
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { icon: LucideIcons2.Minus, label: t("richTextEditor.divider"), onExec: execRule })
38166
38208
  ]
38167
38209
  }
@@ -38202,6 +38244,8 @@ function DocumentPanel({
38202
38244
  id,
38203
38245
  title,
38204
38246
  subtitle,
38247
+ coverImage,
38248
+ icon,
38205
38249
  value,
38206
38250
  actions,
38207
38251
  maxInlineActions,
@@ -38277,62 +38321,74 @@ function DocumentPanel({
38277
38321
  const emitWithId = (event) => () => {
38278
38322
  if (event) eventBus.emit(`UI:${event}`, { id: recordId });
38279
38323
  };
38280
- return /* @__PURE__ */ jsxRuntime.jsx(exports.Card, { variant: "elevated", className: cn("w-full", className), children: /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
38281
- /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "sm", className: "justify-between items-start", children: [
38282
- /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
38283
- titleNode,
38284
- subtitle ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: subtitle }) : null
38285
- ] }),
38286
- /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38287
- /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
38288
- /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
38289
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38290
- editEvent && /* @__PURE__ */ jsxRuntime.jsxs(exports.Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
38291
- /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "edit", size: "xs", className: "mr-1" }),
38292
- editLabel ?? (t("documentPanel.edit") || "Edit")
38293
- ] }),
38294
- inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
38295
- exports.Button,
38296
- {
38297
- variant: "primary",
38298
- size: "sm",
38299
- onClick: () => fireAction(action),
38300
- "data-testid": `action-${action.event}`,
38301
- children: [
38302
- action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
38303
- action.label
38304
- ]
38305
- },
38306
- idx
38307
- )),
38308
- menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
38309
- exports.Menu,
38310
- {
38311
- position: "bottom-end",
38312
- trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
38313
- items: menuActions.map((action) => ({
38314
- label: action.label,
38315
- icon: action.icon,
38316
- variant: action.variant === "danger" ? "danger" : "default",
38317
- onClick: () => fireAction(action)
38318
- }))
38319
- }
38320
- )
38321
- ] }) })
38322
- ] }),
38323
- editing ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
38324
- exports.Box,
38324
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.Card, { variant: "elevated", className: cn("w-full overflow-hidden", className), children: [
38325
+ coverImage ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "h-44 w-full sm:h-52", "data-testid": "document-cover", children: /* @__PURE__ */ jsxRuntime.jsx(
38326
+ "img",
38325
38327
  {
38326
- onClick: emitWithId(editEvent),
38327
- className: cn(
38328
- "rounded-md px-1 -mx-1 min-h-[16rem]",
38329
- editEvent && "cursor-text transition-colors hover:bg-muted/30"
38330
- ),
38331
- "data-testid": "document-body",
38332
- children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
38333
- }
38334
- )
38335
- ] }) });
38328
+ src: coverImage,
38329
+ alt: "",
38330
+ "aria-hidden": "true",
38331
+ className: "h-full w-full object-cover"
38332
+ }
38333
+ ) }) : null,
38334
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", className: "p-6 sm:p-8", children: [
38335
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { gap: "sm", className: "justify-between items-start", children: [
38336
+ /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "xs", className: "flex-1 min-w-0", children: [
38337
+ icon ? /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "text-muted-foreground", "data-testid": "document-icon", children: renderIconInput4(icon, { size: "lg" }) }) : null,
38338
+ titleNode,
38339
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: subtitle }) : null
38340
+ ] }),
38341
+ /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-shrink-0 pt-1 items-center", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38342
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "caption", color: "muted", className: "hidden sm:block", children: autosaveHint ?? (t("documentPanel.autosaveHint") || "") }),
38343
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "primary", size: "sm", onClick: emitWithId(doneEvent), "data-testid": "document-done", children: doneLabel ?? (t("documentPanel.done") || "Done") })
38344
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
38345
+ editEvent && /* @__PURE__ */ jsxRuntime.jsxs(exports.Button, { variant: "ghost", size: "sm", onClick: emitWithId(editEvent), "data-testid": "document-edit", children: [
38346
+ /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "edit", size: "xs", className: "mr-1" }),
38347
+ editLabel ?? (t("documentPanel.edit") || "Edit")
38348
+ ] }),
38349
+ inlineActions.map((action, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
38350
+ exports.Button,
38351
+ {
38352
+ variant: "primary",
38353
+ size: "sm",
38354
+ onClick: () => fireAction(action),
38355
+ "data-testid": `action-${action.event}`,
38356
+ children: [
38357
+ action.icon && renderIconInput4(action.icon, { size: "xs", className: "mr-1" }),
38358
+ action.label
38359
+ ]
38360
+ },
38361
+ idx
38362
+ )),
38363
+ menuActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
38364
+ exports.Menu,
38365
+ {
38366
+ position: "bottom-end",
38367
+ trigger: /* @__PURE__ */ jsxRuntime.jsx(exports.Button, { variant: "ghost", size: "sm", "aria-label": t("common.actions"), "data-testid": "action-overflow", children: /* @__PURE__ */ jsxRuntime.jsx(exports.Icon, { name: "more-horizontal", size: "xs" }) }),
38368
+ items: menuActions.map((action) => ({
38369
+ label: action.label,
38370
+ icon: action.icon,
38371
+ variant: action.variant === "danger" ? "danger" : "default",
38372
+ onClick: () => fireAction(action)
38373
+ }))
38374
+ }
38375
+ )
38376
+ ] }) })
38377
+ ] }),
38378
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, changeEvent: contentChangeEvent, placeholder }) : /* @__PURE__ */ jsxRuntime.jsx(
38379
+ exports.Box,
38380
+ {
38381
+ onClick: emitWithId(editEvent),
38382
+ className: cn(
38383
+ "rounded-md px-1 -mx-1 min-h-[16rem]",
38384
+ editEvent && "cursor-text transition-colors hover:bg-muted/30"
38385
+ ),
38386
+ "data-testid": "document-body",
38387
+ children: value && value !== "" ? /* @__PURE__ */ jsxRuntime.jsx(exports.RichTextEditor, { value, readOnly: true }) : /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "body", color: "muted", children: placeholder ?? (t("documentPanel.placeholder") || "") })
38388
+ }
38389
+ )
38390
+ ] })
38391
+ ] });
38336
38392
  }
38337
38393
  var init_DocumentPanel = __esm({
38338
38394
  "components/core/molecules/DocumentPanel.tsx"() {
@@ -38356,17 +38412,16 @@ function renderIconInput5(icon, props) {
38356
38412
  function fieldName(field) {
38357
38413
  return field.name ?? field.key ?? "";
38358
38414
  }
38359
- function relationLabel(value) {
38360
- if (value === null || typeof value !== "object" || Array.isArray(value) || value instanceof Date) return null;
38361
- for (const key of ["name", "title", "label"]) {
38362
- const candidate = value[key];
38363
- if (typeof candidate === "string" && candidate !== "") return candidate;
38415
+ function relationId(value) {
38416
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
38417
+ const id = value.id;
38418
+ if (id !== void 0 && id !== null) return String(id);
38364
38419
  }
38365
- const id = value.id;
38366
- return id !== void 0 && id !== null ? String(id) : null;
38420
+ return String(value);
38367
38421
  }
38368
38422
  function fieldKind(field, value) {
38369
38423
  if (field.kind) return field.kind;
38424
+ if (field.type === "image") return "image";
38370
38425
  if (field.options && field.options.length > 0) return "select";
38371
38426
  if (typeof value === "boolean" || field.format === "boolean") return "boolean";
38372
38427
  if (Array.isArray(value) || relationLabel(value ?? null) !== null) return "readonly";
@@ -38376,6 +38431,7 @@ function DocumentDetails({
38376
38431
  entity,
38377
38432
  fields,
38378
38433
  metaCommitEvent,
38434
+ relationEvent,
38379
38435
  title,
38380
38436
  className
38381
38437
  }) {
@@ -38390,11 +38446,51 @@ function DocumentDetails({
38390
38446
  if (!metaCommitEvent || next === current) return;
38391
38447
  eventBus.emit(`UI:${metaCommitEvent}`, { id: recordId, patch: { id: recordId, [name]: next } });
38392
38448
  };
38449
+ const relationChip = (item, name, key) => {
38450
+ const label = relationLabel(item) ?? humanizeEnumValue(String(item));
38451
+ if (!relationEvent) {
38452
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", children: label }, key);
38453
+ }
38454
+ const emitClick = () => eventBus.emit(`UI:${relationEvent}`, { field: name, id: relationId(item), name: label });
38455
+ return /* @__PURE__ */ jsxRuntime.jsx(
38456
+ exports.Badge,
38457
+ {
38458
+ variant: "default",
38459
+ role: "button",
38460
+ tabIndex: 0,
38461
+ className: "cursor-pointer transition-colors hover:bg-accent",
38462
+ onClick: emitClick,
38463
+ onKeyDown: (e) => {
38464
+ if (e.key === "Enter" || e.key === " ") {
38465
+ e.preventDefault();
38466
+ emitClick();
38467
+ }
38468
+ },
38469
+ "data-testid": `relation-chip-${name}`,
38470
+ children: label
38471
+ },
38472
+ key
38473
+ );
38474
+ };
38393
38475
  const renderValue = (field) => {
38394
38476
  const name = fieldName(field);
38395
38477
  const raw = getNestedValue(entity ?? {}, name);
38396
38478
  const kind = fieldKind(field, raw);
38397
38479
  const label = field.label ?? field.header ?? humanizeFieldName(name);
38480
+ if (kind === "image") {
38481
+ const url = typeof raw === "string" ? raw : "";
38482
+ if (!url) return /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: "\u2014" });
38483
+ return /* @__PURE__ */ jsxRuntime.jsx(
38484
+ "img",
38485
+ {
38486
+ src: url,
38487
+ alt: label,
38488
+ loading: "lazy",
38489
+ className: "max-h-32 w-full rounded-md border border-border object-cover",
38490
+ "data-testid": `document-property-image-${name}`
38491
+ }
38492
+ );
38493
+ }
38398
38494
  if (kind === "boolean") {
38399
38495
  return /* @__PURE__ */ jsxRuntime.jsx(
38400
38496
  exports.Switch,
@@ -38424,9 +38520,12 @@ function DocumentDetails({
38424
38520
  if (raw.length === 0) {
38425
38521
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", color: "secondary", children: "\u2014" });
38426
38522
  }
38427
- return /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", children: relationLabel(item) ?? humanizeEnumValue(String(item)) }, i)) });
38523
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", className: "flex-wrap", children: raw.map((item, i) => relationChip(item, name, i)) });
38428
38524
  }
38429
- const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : relationLabel(raw) ?? formatValue(raw, field.format);
38525
+ if (raw !== void 0 && raw !== null && raw !== "" && relationLabel(raw) !== null) {
38526
+ return relationChip(raw, name);
38527
+ }
38528
+ const shown2 = raw === void 0 || raw === null || raw === "" ? "\u2014" : formatValue(raw, field.format);
38430
38529
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Typography, { variant: "small", className: "break-words", children: shown2 });
38431
38530
  }
38432
38531
  if (fieldDraft?.name === name) {
@@ -38495,6 +38594,7 @@ var init_DocumentDetails = __esm({
38495
38594
  init_cn();
38496
38595
  init_format();
38497
38596
  init_getNestedValue();
38597
+ init_relationLabel();
38498
38598
  init_useEventBus();
38499
38599
  init_Box();
38500
38600
  init_Stack();
@@ -43716,7 +43816,18 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43716
43816
  if (value === void 0 || value === null) return "\u2014";
43717
43817
  const str2 = String(value);
43718
43818
  switch (fieldType) {
43719
- case "image":
43819
+ case "image": {
43820
+ if (!str2) return "\u2014";
43821
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
43822
+ "img",
43823
+ {
43824
+ src: str2,
43825
+ alt: formatFieldLabel(fieldName2),
43826
+ className: "max-w-full max-h-64 rounded-md object-contain",
43827
+ loading: "lazy"
43828
+ }
43829
+ ) });
43830
+ }
43720
43831
  case "url": {
43721
43832
  if (str2.match(/\.(png|jpe?g|gif|svg|webp|avif)(\?|$)/i) || str2.startsWith("data:image/")) {
43722
43833
  return /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "mt-1 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -43729,7 +43840,7 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43729
43840
  }
43730
43841
  ) });
43731
43842
  }
43732
- if (fieldType === "url" && /^https?:\/\//i.test(str2)) {
43843
+ if (/^https?:\/\//i.test(str2)) {
43733
43844
  return /* @__PURE__ */ jsxRuntime.jsx(
43734
43845
  "a",
43735
43846
  {
@@ -43815,8 +43926,10 @@ function renderRichFieldValue(value, fieldName2, fieldType, meta) {
43815
43926
  return str2;
43816
43927
  }
43817
43928
  case "relation": {
43818
- const match = meta?.options?.find((opt) => opt.value === str2);
43819
- return match ? match.label : str2;
43929
+ const labels = relationDisplayLabels(value, meta?.options);
43930
+ if (labels.length === 0) return "\u2014";
43931
+ if (labels.length === 1) return labels[0];
43932
+ return /* @__PURE__ */ jsxRuntime.jsx(exports.HStack, { gap: "xs", wrap: true, children: labels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx(exports.Badge, { variant: "default", children: label }, i)) });
43820
43933
  }
43821
43934
  case "file": {
43822
43935
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -43918,6 +44031,7 @@ var init_DetailPanel = __esm({
43918
44031
  init_cn();
43919
44032
  init_format();
43920
44033
  init_getNestedValue();
44034
+ init_relationLabel();
43921
44035
  init_useEventBus();
43922
44036
  init_UploadDropZone();
43923
44037
  formatFieldLabel = humanizeFieldName;
@@ -44576,6 +44690,8 @@ function determineInputType(field) {
44576
44690
  return "url";
44577
44691
  case "file":
44578
44692
  return "file";
44693
+ case "image":
44694
+ return "image";
44579
44695
  case "money":
44580
44696
  return "currency";
44581
44697
  case "number":
@@ -45175,6 +45291,49 @@ var init_Form = __esm({
45175
45291
  }
45176
45292
  }
45177
45293
  );
45294
+ case "image": {
45295
+ const imageUrl = typeof currentValue2 === "string" ? currentValue2 : "";
45296
+ return /* @__PURE__ */ jsxRuntime.jsxs(exports.VStack, { gap: "sm", children: [
45297
+ imageUrl ? /* @__PURE__ */ jsxRuntime.jsx(
45298
+ "img",
45299
+ {
45300
+ src: imageUrl,
45301
+ alt: "",
45302
+ "aria-hidden": "true",
45303
+ className: "h-24 w-full max-w-xs rounded-md border border-border object-cover",
45304
+ "data-testid": `image-preview-${fieldName2}`
45305
+ }
45306
+ ) : null,
45307
+ /* @__PURE__ */ jsxRuntime.jsx(
45308
+ exports.Input,
45309
+ {
45310
+ ...commonProps,
45311
+ type: "url",
45312
+ value: imageUrl,
45313
+ onChange: (e) => handleChange(fieldName2, e.target.value)
45314
+ }
45315
+ ),
45316
+ /* @__PURE__ */ jsxRuntime.jsx(
45317
+ exports.UploadDropZone,
45318
+ {
45319
+ accept: "image/*",
45320
+ maxFiles: 1,
45321
+ maxSize: 2 * 1024 * 1024,
45322
+ disabled: isLoading,
45323
+ onFiles: (files) => {
45324
+ const f3 = files[0];
45325
+ if (!f3) return;
45326
+ const reader = new FileReader();
45327
+ reader.onload = () => handleChange(
45328
+ fieldName2,
45329
+ typeof reader.result === "string" ? reader.result : ""
45330
+ );
45331
+ reader.readAsDataURL(f3);
45332
+ }
45333
+ }
45334
+ )
45335
+ ] });
45336
+ }
45178
45337
  case "date":
45179
45338
  return /* @__PURE__ */ jsxRuntime.jsx(
45180
45339
  exports.Input,
@@ -53941,6 +54100,8 @@ var en_default = {
53941
54100
  "richTextEditor.removeLink": "Remove link",
53942
54101
  "richTextEditor.divider": "Divider",
53943
54102
  "richTextEditor.linkPrompt": "Link address",
54103
+ "richTextEditor.image": "Insert image",
54104
+ "richTextEditor.imagePrompt": "Image URL",
53944
54105
  "richTextEditor.placeholder": "Start writing\u2026"
53945
54106
  };
53946
54107
 
@@ -8881,6 +8881,12 @@ interface DocumentPanelProps {
8881
8881
  title?: string;
8882
8882
  /** Muted byline under the title; hidden when empty */
8883
8883
  subtitle?: string;
8884
+ /** Cover/hero image URL painted as a full-bleed banner above the header
8885
+ * row (Notion/article-hero shape); hidden when empty */
8886
+ coverImage?: string;
8887
+ /** Document icon rendered beside the title (a file glyph, a domain icon);
8888
+ * hidden when empty */
8889
+ icon?: IconInput;
8884
8890
  /** The document body as rich-text HTML */
8885
8891
  value?: string;
8886
8892
  /** Header actions (settings, history, …). At most one explicit primary
@@ -8916,7 +8922,7 @@ interface DocumentPanelProps {
8916
8922
  placeholder?: string;
8917
8923
  className?: string;
8918
8924
  }
8919
- declare function DocumentPanel({ id, title, subtitle, value, actions, maxInlineActions, editing, titleCommitEvent, contentChangeEvent, editEvent, doneEvent, editLabel, doneLabel, autosaveHint, placeholder, className, }: DocumentPanelProps): React__default.ReactElement;
8925
+ declare function DocumentPanel({ id, title, subtitle, coverImage, icon, value, actions, maxInlineActions, editing, titleCommitEvent, contentChangeEvent, editEvent, doneEvent, editLabel, doneLabel, autosaveHint, placeholder, className, }: DocumentPanelProps): React__default.ReactElement;
8920
8926
 
8921
8927
  /**
8922
8928
  * DocumentDetails Molecule
@@ -8945,19 +8951,24 @@ interface DocumentDetailsField {
8945
8951
  header?: string;
8946
8952
  /** Lucide icon name or component shown beside the label */
8947
8953
  icon?: IconInput;
8948
- /** How the property edits: text input, boolean switch, select, or display-only.
8949
- * Inferred from the value when omitted (boolean → boolean, array → readonly, else text). */
8950
- kind?: 'text' | 'boolean' | 'select' | 'readonly';
8954
+ /** How the property edits: text input, boolean switch, select, image
8955
+ * thumbnail, or display-only. Inferred from the value when omitted
8956
+ * (boolean boolean, array readonly, image-typed → image, else text). */
8957
+ kind?: 'text' | 'boolean' | 'select' | 'readonly' | 'image';
8951
8958
  /** Options for kind: 'select' */
8952
8959
  options?: readonly string[];
8953
8960
  /** Display format for the read value */
8954
8961
  format?: 'date' | 'currency' | 'number' | 'boolean' | 'percent';
8962
+ /** Entity-declared field type, injected by the display fields contract —
8963
+ * drives kind inference (e.g. `image` → thumbnail row). */
8964
+ type?: string;
8955
8965
  }
8956
8966
  /**
8957
8967
  * DocumentDetails — the document's metadata as a compact settings card for
8958
8968
  * the rail beside a DocumentPanel, each property committing in place.
8959
8969
  *
8960
8970
  * @capabilities document settings, page details, post settings sidebar, metadata panel, document properties, publish settings
8971
+ * @fieldsContract display
8961
8972
  */
8962
8973
  interface DocumentDetailsProps {
8963
8974
  /** The loaded record — supplies the id every commit carries and the
@@ -8971,11 +8982,19 @@ interface DocumentDetailsProps {
8971
8982
  id: string;
8972
8983
  patch: Record<string, FieldValue>;
8973
8984
  }>;
8985
+ /** Makes relation chips clickable: emitted with { field, id, name } when a
8986
+ * hydrated relation value (or one row of a many-relation) is clicked —
8987
+ * the host routes it to the linked record's own surface. */
8988
+ relationEvent?: EventEmit<{
8989
+ field: string;
8990
+ id: string;
8991
+ name: string;
8992
+ }>;
8974
8993
  /** Card heading (defaults to the localized "Details") */
8975
8994
  title?: string;
8976
8995
  className?: string;
8977
8996
  }
8978
- declare function DocumentDetails({ entity, fields, metaCommitEvent, title, className, }: DocumentDetailsProps): React__default.ReactElement | null;
8997
+ declare function DocumentDetails({ entity, fields, metaCommitEvent, relationEvent, title, className, }: DocumentDetailsProps): React__default.ReactElement | null;
8979
8998
 
8980
8999
  /**
8981
9000
  * ReplyTree Organism Component
@@ -8881,6 +8881,12 @@ interface DocumentPanelProps {
8881
8881
  title?: string;
8882
8882
  /** Muted byline under the title; hidden when empty */
8883
8883
  subtitle?: string;
8884
+ /** Cover/hero image URL painted as a full-bleed banner above the header
8885
+ * row (Notion/article-hero shape); hidden when empty */
8886
+ coverImage?: string;
8887
+ /** Document icon rendered beside the title (a file glyph, a domain icon);
8888
+ * hidden when empty */
8889
+ icon?: IconInput;
8884
8890
  /** The document body as rich-text HTML */
8885
8891
  value?: string;
8886
8892
  /** Header actions (settings, history, …). At most one explicit primary
@@ -8916,7 +8922,7 @@ interface DocumentPanelProps {
8916
8922
  placeholder?: string;
8917
8923
  className?: string;
8918
8924
  }
8919
- declare function DocumentPanel({ id, title, subtitle, value, actions, maxInlineActions, editing, titleCommitEvent, contentChangeEvent, editEvent, doneEvent, editLabel, doneLabel, autosaveHint, placeholder, className, }: DocumentPanelProps): React__default.ReactElement;
8925
+ declare function DocumentPanel({ id, title, subtitle, coverImage, icon, value, actions, maxInlineActions, editing, titleCommitEvent, contentChangeEvent, editEvent, doneEvent, editLabel, doneLabel, autosaveHint, placeholder, className, }: DocumentPanelProps): React__default.ReactElement;
8920
8926
 
8921
8927
  /**
8922
8928
  * DocumentDetails Molecule
@@ -8945,19 +8951,24 @@ interface DocumentDetailsField {
8945
8951
  header?: string;
8946
8952
  /** Lucide icon name or component shown beside the label */
8947
8953
  icon?: IconInput;
8948
- /** How the property edits: text input, boolean switch, select, or display-only.
8949
- * Inferred from the value when omitted (boolean → boolean, array → readonly, else text). */
8950
- kind?: 'text' | 'boolean' | 'select' | 'readonly';
8954
+ /** How the property edits: text input, boolean switch, select, image
8955
+ * thumbnail, or display-only. Inferred from the value when omitted
8956
+ * (boolean boolean, array readonly, image-typed → image, else text). */
8957
+ kind?: 'text' | 'boolean' | 'select' | 'readonly' | 'image';
8951
8958
  /** Options for kind: 'select' */
8952
8959
  options?: readonly string[];
8953
8960
  /** Display format for the read value */
8954
8961
  format?: 'date' | 'currency' | 'number' | 'boolean' | 'percent';
8962
+ /** Entity-declared field type, injected by the display fields contract —
8963
+ * drives kind inference (e.g. `image` → thumbnail row). */
8964
+ type?: string;
8955
8965
  }
8956
8966
  /**
8957
8967
  * DocumentDetails — the document's metadata as a compact settings card for
8958
8968
  * the rail beside a DocumentPanel, each property committing in place.
8959
8969
  *
8960
8970
  * @capabilities document settings, page details, post settings sidebar, metadata panel, document properties, publish settings
8971
+ * @fieldsContract display
8961
8972
  */
8962
8973
  interface DocumentDetailsProps {
8963
8974
  /** The loaded record — supplies the id every commit carries and the
@@ -8971,11 +8982,19 @@ interface DocumentDetailsProps {
8971
8982
  id: string;
8972
8983
  patch: Record<string, FieldValue>;
8973
8984
  }>;
8985
+ /** Makes relation chips clickable: emitted with { field, id, name } when a
8986
+ * hydrated relation value (or one row of a many-relation) is clicked —
8987
+ * the host routes it to the linked record's own surface. */
8988
+ relationEvent?: EventEmit<{
8989
+ field: string;
8990
+ id: string;
8991
+ name: string;
8992
+ }>;
8974
8993
  /** Card heading (defaults to the localized "Details") */
8975
8994
  title?: string;
8976
8995
  className?: string;
8977
8996
  }
8978
- declare function DocumentDetails({ entity, fields, metaCommitEvent, title, className, }: DocumentDetailsProps): React__default.ReactElement | null;
8997
+ declare function DocumentDetails({ entity, fields, metaCommitEvent, relationEvent, title, className, }: DocumentDetailsProps): React__default.ReactElement | null;
8979
8998
 
8980
8999
  /**
8981
9000
  * ReplyTree Organism Component