@nivaro/react 0.3.24 → 0.3.26

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.
Files changed (27) hide show
  1. package/dist/hooks/useSessionRecorder.js +1 -1
  2. package/dist/index.d.ts +45 -26
  3. package/dist/shared/components/FilePreviewLightbox.js +56 -35
  4. package/dist/shared/components/ItemEditForm.js +4309 -4263
  5. package/dist/shared/components/PageRenderer.js +63 -62
  6. package/dist/shared/components/RecordReadView.js +229 -229
  7. package/dist/shared/components/ReportView.js +123 -120
  8. package/dist/shared/components/ai/AiMarkdown.js +168 -148
  9. package/dist/shared/components/chat/RoomParts.js +120 -119
  10. package/dist/shared/components/chat/chat-core.js +228 -224
  11. package/dist/shared/components/item-edit/FieldRenderer.js +333 -333
  12. package/dist/shared/components/item-edit/FieldRow.js +413 -394
  13. package/dist/shared/components/item-edit/GroupSection.js +637 -635
  14. package/dist/shared/components/item-edit/InlineGridField.js +89 -90
  15. package/dist/shared/components/item-edit/M2MCombobox.js +117 -116
  16. package/dist/shared/components/item-edit/QuickPicker.js +9 -9
  17. package/dist/shared/components/item-edit/RawEditSheet.js +80 -79
  18. package/dist/shared/components/item-edit/RelationCombobox.js +173 -167
  19. package/dist/shared/components/item-edit/RowMatchPanel.js +1 -1
  20. package/dist/shared/components/item-edit/m2m-invalidate.js +19 -0
  21. package/dist/shared/components/item-edit/record-lookups.js +15 -0
  22. package/dist/shared/components/item-edit/render-stability.js +147 -0
  23. package/dist/shared/components/layout-editor/FormMapButton.js +5 -4
  24. package/dist/shared/lib/picker-rank.js +21 -0
  25. package/dist/shared/lib/safe-href.js +10 -0
  26. package/dist/shared/lib/socket-realtime.js +107 -78
  27. package/package.json +6 -6
@@ -9,7 +9,7 @@ import { noteReplayEvent as _e, createReplayCheckoutState as De } from "../share
9
9
  import { emitReplayTap as Te, setReplayRecorderMode as N, registerReplayCapture as qe, clipEndOffset as Le, withClipKind as Pe } from "../shared/lib/replay-capture.js";
10
10
  import { instrumentConsole as Ne, instrumentRequests as Fe, redactSecrets as Me } from "../shared/lib/replay-context.js";
11
11
  import { instrumentSocket as Oe } from "../shared/lib/replay-socket.js";
12
- const Ue = "nivaro-react@0.3.24", j = 1e4, We = 150, He = 3e3, Ke = 600, $e = (n) => `nvr_replay_resume${n ? `:${n}` : ""}`, G = () => typeof navigator > "u" || navigator.onLine !== !1;
12
+ const Ue = "nivaro-react@0.3.26", j = 1e4, We = 150, He = 3e3, Ke = 600, $e = (n) => `nvr_replay_resume${n ? `:${n}` : ""}`, G = () => typeof navigator > "u" || navigator.onLine !== !1;
13
13
  function Be(n) {
14
14
  return typeof n.url != "string" || typeof fetch != "function" ? null : (f, a) => {
15
15
  const u = typeof n.getToken == "function" ? n.getToken() : void 0, d = { "Content-Type": "application/json" };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ import { ListResponse } from '@nivaro/sdk';
13
13
  import type { LucideIcon } from 'lucide-react';
14
14
  import { ManagedUser } from '@nivaro/sdk';
15
15
  import { MutableRefObject } from 'react';
16
+ import { NamedExoticComponent } from 'react';
16
17
  import { NivaroClient } from '@nivaro/sdk';
17
18
  import { Query } from '@nivaro/sdk';
18
19
  import { QueryClient } from '@tanstack/react-query';
@@ -3070,30 +3071,32 @@ export declare interface FieldInterfaceProps {
3070
3071
  itemId: string | null;
3071
3072
  }
3072
3073
 
3073
- export declare function FieldRenderer({ field, value, onChange, relations, collection, itemId, cascadeFilter, requiredParentLabel, narrowedBy, onCountChange, displayOnly, prefillParentId, pinnedOption }: {
3074
- field: CMSField;
3075
- value: unknown;
3076
- onChange: (v: unknown) => void;
3077
- relations: CMSRelation[];
3078
- collection: string;
3079
- itemId: string;
3080
- cascadeFilter?: Record<string, unknown>;
3081
- requiredParentLabel?: string | null;
3082
- /** Cascade parents currently narrowing an M2O picker (see RelationCombobox). */
3083
- narrowedBy?: {
3084
- labels: string[];
3085
- keys: string[];
3086
- onClear?: (key: string) => void;
3087
- };
3088
- onCountChange?: (count: number) => void;
3089
- displayOnly?: boolean;
3090
- prefillParentId?: string;
3091
- /** M2O pickers: a floating first option (see RelationCombobox `pinned`). */
3092
- pinnedOption?: {
3093
- id: unknown;
3094
- tag?: string;
3095
- } | null;
3096
- }): JSX.Element;
3074
+ /** Memoized: rows pass it stable props, so a row re-render that changes
3075
+ * nothing about this field's control skips it. */
3076
+ export declare const FieldRenderer: NamedExoticComponent< {
3077
+ field: CMSField;
3078
+ value: unknown;
3079
+ onChange: (v: unknown) => void;
3080
+ relations: CMSRelation[];
3081
+ collection: string;
3082
+ itemId: string;
3083
+ cascadeFilter?: Record<string, unknown>;
3084
+ requiredParentLabel?: string | null;
3085
+ /** Cascade parents currently narrowing an M2O picker (see RelationCombobox). */
3086
+ narrowedBy?: {
3087
+ labels: string[];
3088
+ keys: string[];
3089
+ onClear?: (key: string) => void;
3090
+ };
3091
+ onCountChange?: (count: number) => void;
3092
+ displayOnly?: boolean;
3093
+ prefillParentId?: string;
3094
+ /** M2O pickers: a floating first option (see RelationCombobox `pinned`). */
3095
+ pinnedOption?: {
3096
+ id: unknown;
3097
+ tag?: string;
3098
+ } | null;
3099
+ }>;
3097
3100
 
3098
3101
  export declare type FieldState = {
3099
3102
  value: unknown;
@@ -5075,6 +5078,13 @@ export declare interface ItemEditFormProps {
5075
5078
  /** Extra rows for the record's More menu, above Delete (e.g. a host's Edit layout).
5076
5079
  * Rows that open something outside the menu set `data-nvr-menu-close`. */
5077
5080
  moreMenuItems?: ReactNode;
5081
+ /** Which view the record shows (Summary or Edit) and the collection's Summary layout id
5082
+ * (null = none; Summary then renders the edit layout read-only). Hosts use it so their
5083
+ * Edit layout opens the layout the person is looking at. */
5084
+ onViewModeChange?: (view: {
5085
+ mode: 'summary' | 'edit';
5086
+ summaryLayoutId: number | null;
5087
+ }) => void;
5078
5088
  /** Scroll to + flash a field on load (deep links: ?focus=<field>). */
5079
5089
  focusField?: string | null;
5080
5090
  /**
@@ -7308,8 +7318,10 @@ export declare function PretendDownSection({ scope, options }: {
7308
7318
 
7309
7319
  /**
7310
7320
  * Inline attachment preview — click a file, see it, without a download round
7311
- * trip. Images, PDFs, video and audio render in an overlay; anything else
7312
- * gets an honest "no preview" panel with the Download action front and center.
7321
+ * trip. Images (SVG too, drawn by an img tag, which runs no script), PDFs,
7322
+ * video, audio and plain text render in an overlay; anything else (HTML, XML,
7323
+ * Office files…) gets an honest "no preview" panel with the Download action
7324
+ * front and center. Plain text loads in a fully sandboxed iframe.
7313
7325
  *
7314
7326
  * Plain portal to document.body at z-[130] (above popovers at 110 and the
7315
7327
  * tooltip layer at 120). The asset URL is the same cookie/session-authed
@@ -9537,6 +9549,13 @@ export declare interface SocketRealtimeOptions {
9537
9549
  windowEvents?: Partial<Record<SocketRealtimeWindowEvent, boolean>>;
9538
9550
  /** How long a reconnect waits for its catch-up answer before it counts as live again. */
9539
9551
  catchupTimeoutMs?: number;
9552
+ /**
9553
+ * Upper bound of the random delay before a `catchup:full` or `access:changed` window event
9554
+ * (default REACTION_JITTER_MS; 0 = at once). Both land on every tab at the same moment after a
9555
+ * deploy or a role change, and each makes the tab refetch its lists — spread over a few
9556
+ * seconds they stop arriving at the API as one wave.
9557
+ */
9558
+ reactionJitterMs?: number;
9540
9559
  }
9541
9560
 
9542
9561
  export declare type SocketRealtimeWindowEvent = 'catchupFull' | 'accessChanged' | 'configUpdate' | 'forceRefresh';
@@ -1,77 +1,98 @@
1
- import { jsx as t, jsxs as n } from "react/jsx-runtime";
2
- import { Download as c, X as d } from "lucide-react";
3
- import { useEffect as m } from "react";
1
+ import { jsx as t, jsxs as s } from "react/jsx-runtime";
2
+ import { Download as d, X as x } from "lucide-react";
3
+ import { useEffect as u } from "react";
4
4
  import { createPortal as h } from "react-dom";
5
- function v(e) {
6
- return e ? e.startsWith("image/") || e === "application/pdf" || e.startsWith("video/") || e.startsWith("audio/") || e.startsWith("text/") : !1;
5
+ const p = /* @__PURE__ */ new Set([
6
+ "text/plain",
7
+ "text/csv",
8
+ "text/tab-separated-values",
9
+ "text/markdown",
10
+ "text/x-markdown",
11
+ "text/x-log"
12
+ ]);
13
+ function o(e) {
14
+ return String(e ?? "").split(";")[0].trim().toLowerCase();
7
15
  }
8
- function x(e) {
16
+ function m(e) {
17
+ return p.has(o(e));
18
+ }
19
+ function N(e) {
20
+ if (!e) return !1;
21
+ const r = o(e);
22
+ return r.startsWith("image/") || r === "application/pdf" || r.startsWith("video/") || r.startsWith("audio/") || m(r);
23
+ }
24
+ function w(e) {
9
25
  return e == null || !Number.isFinite(e) ? null : e < 1024 ? `${e} B` : e < 1024 * 1024 ? `${(e / 1024).toFixed(1)} KB` : `${(e / (1024 * 1024)).toFixed(1)} MB`;
10
26
  }
11
- function b({
12
- file: e,
13
- onClose: s
14
- }) {
15
- m(() => {
16
- const a = (l) => {
17
- l.key === "Escape" && (l.stopPropagation(), s());
27
+ function y({ file: e, onClose: r }) {
28
+ u(() => {
29
+ const n = (c) => {
30
+ c.key === "Escape" && (c.stopPropagation(), r());
18
31
  };
19
- return window.addEventListener("keydown", a, !0), () => window.removeEventListener("keydown", a, !0);
20
- }, [s]);
21
- const r = e.type ?? "", o = x(e.size);
32
+ return window.addEventListener("keydown", n, !0), () => window.removeEventListener("keydown", n, !0);
33
+ }, [r]);
34
+ const a = e.type ?? "", l = w(e.size);
22
35
  let i;
23
- return r.startsWith("image/") ? i = /* @__PURE__ */ t(
36
+ return a.startsWith("image/") ? i = /* @__PURE__ */ t(
24
37
  "img",
25
38
  {
26
39
  src: e.url,
27
40
  alt: e.name,
28
41
  className: "max-h-[82vh] max-w-full rounded object-contain"
29
42
  }
30
- ) : r === "application/pdf" || r.startsWith("text/") ? i = /* @__PURE__ */ t(
43
+ ) : o(a) === "application/pdf" ? i = /* @__PURE__ */ t(
44
+ "iframe",
45
+ {
46
+ src: e.url,
47
+ title: e.name,
48
+ className: "h-[82vh] w-[min(920px,92vw)] rounded border-0 bg-white"
49
+ }
50
+ ) : m(a) ? i = /* @__PURE__ */ t(
31
51
  "iframe",
32
52
  {
33
53
  src: e.url,
34
54
  title: e.name,
55
+ sandbox: "",
35
56
  className: "h-[82vh] w-[min(920px,92vw)] rounded border-0 bg-white"
36
57
  }
37
- ) : r.startsWith("video/") ? i = /* @__PURE__ */ t("video", { src: e.url, controls: !0, className: "max-h-[82vh] max-w-full rounded" }) : r.startsWith("audio/") ? i = /* @__PURE__ */ t("audio", { src: e.url, controls: !0, className: "w-[min(480px,90vw)]" }) : i = /* @__PURE__ */ n("div", { className: "flex w-[min(420px,90vw)] flex-col items-center gap-3 rounded-lg bg-white p-8 text-center dark:bg-card", children: [
58
+ ) : a.startsWith("video/") ? i = /* @__PURE__ */ t("video", { src: e.url, controls: !0, className: "max-h-[82vh] max-w-full rounded" }) : a.startsWith("audio/") ? i = /* @__PURE__ */ t("audio", { src: e.url, controls: !0, className: "w-[min(480px,90vw)]" }) : i = /* @__PURE__ */ s("div", { className: "flex w-[min(420px,90vw)] flex-col items-center gap-3 rounded-lg bg-white p-8 text-center dark:bg-card", children: [
38
59
  /* @__PURE__ */ t("p", { className: "text-[13px] font-medium text-slate-700 dark:text-slate-200", children: e.name }),
39
- /* @__PURE__ */ n("p", { className: "text-[12px] text-slate-500 dark:text-slate-400", children: [
60
+ /* @__PURE__ */ s("p", { className: "text-[12px] text-slate-500 dark:text-slate-400", children: [
40
61
  "No inline preview for this file type",
41
- r ? ` (${r})` : "",
62
+ a ? ` (${a})` : "",
42
63
  "."
43
64
  ] }),
44
- /* @__PURE__ */ n(
65
+ /* @__PURE__ */ s(
45
66
  "a",
46
67
  {
47
68
  href: `${e.url}?download=1`,
48
69
  className: "inline-flex items-center gap-1.5 rounded-md bg-nvr-cyan px-3 py-1.5 text-[12.5px] font-medium text-white hover:brightness-110",
49
70
  children: [
50
- /* @__PURE__ */ t(c, { className: "h-3.5 w-3.5" }),
71
+ /* @__PURE__ */ t(d, { className: "h-3.5 w-3.5" }),
51
72
  " Download"
52
73
  ]
53
74
  }
54
75
  )
55
76
  ] }), h(
56
77
  // biome-ignore lint/a11y/useKeyWithClickEvents: Escape handled globally above
57
- /* @__PURE__ */ n(
78
+ /* @__PURE__ */ s(
58
79
  "div",
59
80
  {
60
81
  className: "fixed inset-0 z-[130] flex flex-col bg-black/70 backdrop-blur-[2px]",
61
- onClick: s,
82
+ onClick: r,
62
83
  "data-file-lightbox": !0,
63
84
  children: [
64
- /* @__PURE__ */ n(
85
+ /* @__PURE__ */ s(
65
86
  "div",
66
87
  {
67
88
  className: "flex items-center gap-3 px-4 py-2.5",
68
- onClick: (a) => a.stopPropagation(),
89
+ onClick: (n) => n.stopPropagation(),
69
90
  onKeyDown: () => {
70
91
  },
71
92
  children: [
72
- /* @__PURE__ */ n("p", { className: "min-w-0 flex-1 truncate text-[13px] font-medium text-white", children: [
93
+ /* @__PURE__ */ s("p", { className: "min-w-0 flex-1 truncate text-[13px] font-medium text-white", children: [
73
94
  e.name,
74
- o && /* @__PURE__ */ t("span", { className: "ml-2 text-[11px] font-normal text-white/60", children: o })
95
+ l && /* @__PURE__ */ t("span", { className: "ml-2 text-[11px] font-normal text-white/60", children: l })
75
96
  ] }),
76
97
  /* @__PURE__ */ t(
77
98
  "a",
@@ -79,17 +100,17 @@ function b({
79
100
  href: `${e.url}?download=1`,
80
101
  title: "Download",
81
102
  className: "rounded p-1.5 text-white/80 hover:bg-white/10 hover:text-white",
82
- children: /* @__PURE__ */ t(c, { className: "h-4 w-4" })
103
+ children: /* @__PURE__ */ t(d, { className: "h-4 w-4" })
83
104
  }
84
105
  ),
85
106
  /* @__PURE__ */ t(
86
107
  "button",
87
108
  {
88
109
  type: "button",
89
- onClick: s,
110
+ onClick: r,
90
111
  title: "Close (Esc)",
91
112
  className: "rounded p-1.5 text-white/80 hover:bg-white/10 hover:text-white",
92
- children: /* @__PURE__ */ t(d, { className: "h-4 w-4" })
113
+ children: /* @__PURE__ */ t(x, { className: "h-4 w-4" })
93
114
  }
94
115
  )
95
116
  ]
@@ -99,7 +120,7 @@ function b({
99
120
  "div",
100
121
  {
101
122
  className: "flex min-h-0 flex-1 items-center justify-center p-4 pt-0",
102
- onClick: (a) => a.stopPropagation(),
123
+ onClick: (n) => n.stopPropagation(),
103
124
  onKeyDown: () => {
104
125
  },
105
126
  children: i
@@ -112,6 +133,6 @@ function b({
112
133
  );
113
134
  }
114
135
  export {
115
- b as FilePreviewLightbox,
116
- v as canPreviewFile
136
+ y as FilePreviewLightbox,
137
+ N as canPreviewFile
117
138
  };