@nivaro/react 0.1.33 → 0.1.34

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.
package/dist/index.d.ts CHANGED
@@ -58,6 +58,8 @@ export declare interface ApiVersionInfo {
58
58
  environment?: string;
59
59
  }
60
60
 
61
+ declare type BinaryOp = '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||';
62
+
61
63
  export declare function BooleanField({ field, value, onChange, error, disabled, readOnly, inputId, errorId }: FieldComponentProps): JSX.Element;
62
64
 
63
65
  export declare function canOpenChatRoom(): boolean;
@@ -72,6 +74,7 @@ declare interface ChannelMeta {
72
74
  role: string | null;
73
75
  topic: string | null;
74
76
  created_by: string | null;
77
+ is_direct?: boolean;
75
78
  }
76
79
 
77
80
  export declare function chatAvatarColor(id: string): string;
@@ -122,25 +125,6 @@ export declare interface ChatConfig {
122
125
 
123
126
  export declare function chatInitials(name: string | null): string;
124
127
 
125
- /**
126
- * Nivaro chat — data layer.
127
- *
128
- * Messages, rooms and watermarks go through /api/chat, NOT plain /items: room
129
- * visibility (your DMs, the channels you belong to, entity rooms for records
130
- * you can read) cannot be expressed as a table-level policy, and the items API
131
- * now refuses these collections outright. Presence/typing stay on /items,
132
- * which is per-user by nature. Live delivery comes from a host-provided
133
- * realtime adapter, with polling fallbacks baked into every query.
134
- *
135
- * Ported from the EFP implementation with its semantics preserved:
136
- * - DM room keys are 'dm:<A>:<B>' with UPPERCASED sorted uuids (MSSQL returns
137
- * uuids uppercased — a casing mismatch forks a second room).
138
- * - Read watermarks are serialized per room (markInFlight) because the reads
139
- * table has UNIQUE(user, room) and the room view marks on entry AND on each
140
- * new message — concurrent select-then-insert would 500 on duplicate key.
141
- * - Mentions store '@[Display Name]' with no id; notifications fire only for
142
- * users picked from the autocomplete.
143
- */
144
128
  export declare interface ChatMessage {
145
129
  id: number;
146
130
  sender: string;
@@ -148,6 +132,10 @@ export declare interface ChatMessage {
148
132
  room: string;
149
133
  message: string;
150
134
  date_created: string;
135
+ edited_at?: string | null;
136
+ deleted_at?: string | null;
137
+ attachments?: string[];
138
+ reactions?: ChatReaction[];
151
139
  }
152
140
 
153
141
  export declare interface ChatOnlineUser {
@@ -204,12 +192,39 @@ declare interface ChatProviderProps {
204
192
  theme?: Partial<ChatTheme>;
205
193
  }
206
194
 
207
- export declare function ChatRoomList({ rooms, onOpen }: {
195
+ /**
196
+ * Nivaro chat — data layer.
197
+ *
198
+ * Messages, rooms and watermarks go through /api/chat, NOT plain /items: room
199
+ * visibility (your DMs, the channels you belong to, entity rooms for records
200
+ * you can read) cannot be expressed as a table-level policy, and the items API
201
+ * now refuses these collections outright. Presence/typing stay on /items,
202
+ * which is per-user by nature. Live delivery comes from a host-provided
203
+ * realtime adapter, with polling fallbacks baked into every query.
204
+ *
205
+ * Ported from the EFP implementation with its semantics preserved:
206
+ * - DM room keys are 'dm:<A>:<B>' with UPPERCASED sorted uuids (MSSQL returns
207
+ * uuids uppercased — a casing mismatch forks a second room).
208
+ * - Read watermarks are serialized per room (markInFlight) because the reads
209
+ * table has UNIQUE(user, room) and the room view marks on entry AND on each
210
+ * new message — concurrent select-then-insert would 500 on duplicate key.
211
+ * - Mentions store '@[Display Name]' with no id; notifications fire only for
212
+ * users picked from the autocomplete.
213
+ */
214
+ declare interface ChatReaction {
215
+ emoji: string;
216
+ user: string;
217
+ user_name: string | null;
218
+ }
219
+
220
+ export declare function ChatRoomList({ rooms, onOpen, onNewGroup }: {
208
221
  rooms: RoomInfo[];
209
222
  onOpen: (room: RoomInfo) => void;
223
+ /** Opens the group-conversation composer (host renders the dialog). */
224
+ onNewGroup?: () => void;
210
225
  }): JSX.Element;
211
226
 
212
- export declare function ChatRoomView({ room, label, onBack, onOpenSettings, renderMessageBody }: {
227
+ export declare function ChatRoomView({ room, label, onBack, onOpenSettings, renderMessageBody, initialUnread }: {
213
228
  room: string;
214
229
  label: string;
215
230
  onBack: () => void;
@@ -218,6 +233,8 @@ export declare function ChatRoomView({ room, label, onBack, onOpenSettings, rend
218
233
  renderMessageBody?: (m: ChatMessage, ctx: {
219
234
  mine: boolean;
220
235
  }) => React.ReactNode;
236
+ /** Unread count at open — anchors the "New messages" divider. */
237
+ initialUnread?: number;
221
238
  }): JSX.Element;
222
239
 
223
240
  /**
@@ -461,6 +478,88 @@ export declare function effectiveScopeSeedIds(scopes: {
461
478
  restricted: Record<string, Array<string | number>>;
462
479
  }, dimName: string): Array<string | number>;
463
480
 
481
+ /** Boolean convenience — for guards and conditions. */
482
+ export declare function evaluateBoolean(src: string, row: TokenResolver | Record<string, unknown>, opts?: EvaluateOptions): boolean;
483
+
484
+ /** Parse and evaluate in one call. Returns null on a parse error. */
485
+ export declare function evaluateExpression(src: string, resolve: TokenResolver | Record<string, unknown>, opts?: EvaluateOptions): ExprValue;
486
+
487
+ /** Numeric convenience — the shape every existing call site wants. */
488
+ export declare function evaluateNumeric(src: string, row: TokenResolver | Record<string, unknown>, opts?: EvaluateOptions): number | null;
489
+
490
+ declare interface EvaluateOptions {
491
+ /**
492
+ * What an unresolvable or non-numeric token means in arithmetic.
493
+ * 'zero' (default) matches every existing dialect. 'null' propagates, so a
494
+ * formula over a missing field shows nothing rather than a wrong number.
495
+ */
496
+ missing?: 'zero' | 'null';
497
+ }
498
+
499
+ declare type ExprNode = {
500
+ kind: 'number';
501
+ value: number;
502
+ } | {
503
+ kind: 'string';
504
+ value: string;
505
+ } | {
506
+ kind: 'boolean';
507
+ value: boolean;
508
+ } | {
509
+ kind: 'null';
510
+ } | {
511
+ kind: 'token';
512
+ path: string;
513
+ } | {
514
+ kind: 'call';
515
+ name: string;
516
+ args: ExprNode[];
517
+ } | {
518
+ kind: 'unary';
519
+ op: '-' | '!';
520
+ operand: ExprNode;
521
+ } | {
522
+ kind: 'binary';
523
+ op: BinaryOp;
524
+ left: ExprNode;
525
+ right: ExprNode;
526
+ };
527
+
528
+ /**
529
+ * One expression language for the `{{token}}` formulas users author.
530
+ *
531
+ * Nivaro grew five separate arithmetic dialects — inline-grid client formulas,
532
+ * QueryTable column formulas, match-agg column formulas, allocate-drawer
533
+ * formulas and rollup value formulas — each with its own parser, its own token
534
+ * regex and its own failure behaviour. A user cannot tell which box takes which
535
+ * syntax, and every one of them shared the same two defects:
536
+ *
537
+ * 1. They SUBSTITUTED token values into the string and then parsed (or, in
538
+ * two cases, `eval`ed) the result. A value that is not a bare number —
539
+ * a negative, a date, a string, anything — corrupted the expression into
540
+ * something that either threw or, worse, silently computed a different
541
+ * sum than the author meant.
542
+ * 2. A missing or non-numeric token became `0` with no signal, so a formula
543
+ * referencing a field that had been renamed quietly produced a plausible
544
+ * wrong number instead of an error.
545
+ *
546
+ * This module parses to an AST once and evaluates against a resolver, so a
547
+ * token's VALUE is never able to change the SHAPE of the expression. It also
548
+ * separates the two questions those dialects conflated: "is this expression
549
+ * well-formed" (answerable at authoring time, which is what makes an editor
550
+ * possible) from "what does it evaluate to for this row".
551
+ *
552
+ * `missing: 'zero'` is the DEFAULT and preserves the existing behaviour that
553
+ * live formulas depend on — `{{amount}} - {{allocated_total}}` must keep
554
+ * working when nothing has been allocated yet. `missing: 'null'` opts into
555
+ * propagation for new call sites that would rather show nothing than a wrong
556
+ * number.
557
+ */
558
+ export declare type ExprValue = number | string | boolean | null;
559
+
560
+ /** Field paths an expression reads, without evaluating it. */
561
+ export declare function extractExpressionTokens(src: string): string[];
562
+
464
563
  declare type Fetcher = () => Promise<ApiVersionInfo | null>;
465
564
 
466
565
  export declare function fetchSchema(client: NivaroClient, collection: string, includeHidden: boolean, layoutId?: number, layoutSlug?: string): Promise<FormSchema>;
@@ -639,6 +738,50 @@ export declare type FormStatus = {
639
738
  canSubmit: boolean;
640
739
  };
641
740
 
741
+ /**
742
+ * Authoring surface for `{{token}}` formulas.
743
+ *
744
+ * The point is that a formula can be wrong in three distinct ways, and until
745
+ * now none of them said anything: a syntax error rendered as a blank cell, a
746
+ * renamed field silently became zero, and the author could not see what the
747
+ * expression actually produced without saving and going to look. Each gets its
748
+ * own signal here — a parse error with a caret position, an unknown-field
749
+ * warning, and a live result computed against a real record.
750
+ */
751
+ export declare function FormulaEditor({ value, onChange, fields, sample, sampleLabel, placeholder, rows, serverEvaluated, className }: FormulaEditorProps): JSX.Element;
752
+
753
+ export declare interface FormulaEditorProps {
754
+ value: string;
755
+ onChange: (value: string) => void;
756
+ /** Fields offered by autocomplete and checked against for typos. */
757
+ fields: FormulaField[];
758
+ /**
759
+ * A record to evaluate against, so the author sees the answer rather than
760
+ * guessing. Omitted = no preview, which is honest rather than showing a
761
+ * result computed from nothing.
762
+ */
763
+ sample?: Record<string, unknown> | null;
764
+ /** Labels the preview, e.g. the id of the record it was computed from. */
765
+ sampleLabel?: string;
766
+ placeholder?: string;
767
+ rows?: number;
768
+ /**
769
+ * Set for formulas the SERVER evaluates (stored rollups, write-computed
770
+ * fields). The preview is then explicitly labelled as an approximation,
771
+ * because the server resolves relations this editor cannot see.
772
+ */
773
+ serverEvaluated?: boolean;
774
+ className?: string;
775
+ }
776
+
777
+ export declare interface FormulaField {
778
+ /** Token path, e.g. `amount` or `line.total`. */
779
+ field: string;
780
+ label?: string;
781
+ /** Shown in the picker so an author can tell a currency from a count. */
782
+ type?: string;
783
+ }
784
+
642
785
  /** A single validation rule attached to a field. */
643
786
  export declare type FormValidationRule = {
644
787
  type: string;
@@ -843,7 +986,7 @@ declare type ItemEditAuthContextValue = {
843
986
  userId: string;
844
987
  };
845
988
 
846
- export declare function ItemEditForm({ collection, itemId: itemIdProp, layoutSlug, onBack, onSaved, onDeleted, showHeader, showItemActions, showRevisions, showClone, showPipeline, showWorkflow, showComments, showTasks, showLockBanner, className, headerClassName, renderField, extraTopContent, extraBottomContent, onHeaderWidgets, initialImportResult, initialValues, initialLinks, initialRows }: ItemEditFormProps): JSX.Element;
989
+ export declare function ItemEditForm({ collection, itemId: itemIdProp, layoutSlug, onBack, onSaved, onDeleted, showHeader, showItemActions, onDuplicate, showRevisions, showClone, showPipeline, showWorkflow, showComments, showTasks, showLockBanner, className, headerClassName, renderField, extraTopContent, extraBottomContent, onHeaderWidgets, initialImportResult, initialValues, initialLinks, initialRows }: ItemEditFormProps): JSX.Element;
847
990
 
848
991
  export declare interface ItemEditFormProps {
849
992
  collection: string;
@@ -857,6 +1000,12 @@ export declare interface ItemEditFormProps {
857
1000
  * header toolbar. Off by default — the admin's ItemEdit page renders its
858
1001
  * own copy in the page header; headless hosts opt in. */
859
1002
  showItemActions?: boolean;
1003
+ /**
1004
+ * When set, a Duplicate button renders on saved records. Receives a prefill
1005
+ * object (plain scalar + M2O values; audit/auto-id/computed/alias fields
1006
+ * excluded) — the host navigates to its new-record route with it.
1007
+ */
1008
+ onDuplicate?: (prefill: Record<string, unknown>) => void;
860
1009
  showRevisions?: boolean;
861
1010
  showClone?: boolean;
862
1011
  showPipeline?: boolean;
@@ -1151,6 +1300,24 @@ export declare interface PageRendererWidget {
1151
1300
  config?: Record<string, unknown>;
1152
1301
  }
1153
1302
 
1303
+ export declare function parseExpression(src: string): ParseResult;
1304
+
1305
+ declare interface ParseFailure {
1306
+ ok: false;
1307
+ error: string;
1308
+ /** Zero-based index into the source where the problem is, for editor carets. */
1309
+ position: number;
1310
+ }
1311
+
1312
+ declare type ParseResult = ParseSuccess | ParseFailure;
1313
+
1314
+ declare interface ParseSuccess {
1315
+ ok: true;
1316
+ ast: ExprNode;
1317
+ /** Every distinct token path referenced, in first-appearance order. */
1318
+ tokens: string[];
1319
+ }
1320
+
1154
1321
  declare interface PendingTask {
1155
1322
  title: string;
1156
1323
  assignee: string | null;
@@ -1413,6 +1580,7 @@ export declare interface RoomInfo {
1413
1580
  lastMessage: ChatMessage | null;
1414
1581
  unread: number;
1415
1582
  muted: boolean;
1583
+ notify_mode: 'all' | 'mentions';
1416
1584
  joined: boolean;
1417
1585
  channel: ChannelMeta | null;
1418
1586
  }
@@ -1514,6 +1682,8 @@ export declare function TextField({ field, value, onChange, error, disabled, rea
1514
1682
 
1515
1683
  export declare function TipLayer(): ReactPortal | null;
1516
1684
 
1685
+ declare type TokenResolver = (path: string) => unknown;
1686
+
1517
1687
  /** Install the listeners. Safe to call more than once. */
1518
1688
  export declare function trackActivity(): void;
1519
1689
 
@@ -1739,6 +1909,7 @@ export declare function useSectionState(form: UseNivaroFormReturn, defaultCollap
1739
1909
  export declare function useSendChatMessage(room: string): UseMutationResult<void, Error, string | {
1740
1910
  text: string;
1741
1911
  mentions?: string[];
1912
+ attachments?: string[];
1742
1913
  }, unknown>;
1743
1914
 
1744
1915
  export declare function useSessionRecorder(options?: SessionRecorderOptions): void;
@@ -1755,6 +1926,23 @@ export declare function useUnreadChirp(totalUnread: number, rooms?: RoomInfo[]):
1755
1926
 
1756
1927
  export declare function useWatchFields(form: UseNivaroFormReturn, fields: string[]): Record<string, unknown>;
1757
1928
 
1929
+ /**
1930
+ * Authoring-time check. The point of separating this from evaluation is that
1931
+ * a formula can be told it is wrong while it is being written, instead of
1932
+ * silently producing nothing at render time months later.
1933
+ */
1934
+ export declare function validateExpression(src: string, knownFields?: string[]): ValidationResult;
1935
+
1936
+ declare interface ValidationResult {
1937
+ ok: boolean;
1938
+ error?: string;
1939
+ position?: number;
1940
+ /** Every field the expression reads. */
1941
+ tokens: string[];
1942
+ /** Tokens not present in the supplied field list — probably typos or renames. */
1943
+ unknownTokens: string[];
1944
+ }
1945
+
1758
1946
  export declare function WorkflowPanel({ collection, item }: {
1759
1947
  collection: string;
1760
1948
  item: string;