@object-ui/fields 3.3.2 → 4.0.1

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.
@@ -121,6 +121,31 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
121
121
  const lookupPageSize: number | undefined = fieldMeta?.lookup_page_size;
122
122
  const lookupFilters: import('@object-ui/types').LookupFilterDef[] | undefined = fieldMeta?.lookup_filters;
123
123
 
124
+ /**
125
+ * Dependent lookups — restrict candidates based on values of *other* fields
126
+ * in the same form. Two shapes are accepted:
127
+ *
128
+ * 1. `depends_on: ['country']` → shorthand. The dependent field value is sent
129
+ * as both the filter field and the source field (i.e. `country = ${country}`).
130
+ * 2. `depends_on: [{ field: 'country', param: 'country_id' }]` → explicit.
131
+ * The remote field name (`param`) can differ from the local field name.
132
+ *
133
+ * When any dependency is empty, the lookup is gated and the user sees a
134
+ * helpful "Select {field} first" hint instead of unfiltered records.
135
+ */
136
+ const dependsOn = useMemo<Array<{ field: string; param: string }>>(() => {
137
+ const raw = fieldMeta?.depends_on ?? fieldMeta?.dependsOn;
138
+ if (!raw) return [];
139
+ if (Array.isArray(raw)) {
140
+ return raw.map((d: any) =>
141
+ typeof d === 'string' ? { field: d, param: d } : { field: d.field, param: d.param ?? d.field },
142
+ );
143
+ }
144
+ return [];
145
+ }, [fieldMeta?.depends_on, fieldMeta?.dependsOn]);
146
+
147
+ // Resolve dependent field values from explicit prop or SchemaRendererContext.data
148
+ const dependentValuesProp = (props as any).dependentValues as Record<string, any> | undefined;
124
149
  // Derive filter columns from lookup_columns that have type info
125
150
  const filterColumns = useMemo<RecordPickerFilterColumn[] | undefined>(() => {
126
151
  if (!lookupColumns) return undefined;
@@ -146,6 +171,24 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
146
171
  const dataSource: DataSource | null =
147
172
  (props as any).dataSource ?? lookupField?.dataSource ?? fieldMeta?.dataSource ?? contextDataSource;
148
173
 
174
+ /** Resolve dependent values from the explicit prop (preferred), the form-data
175
+ * context provided by @object-ui/react, or finally `ctx.data` (record scope). */
176
+ const resolvedDependentValues: Record<string, any> = useMemo(() => {
177
+ if (dependentValuesProp) return dependentValuesProp;
178
+ return (ctx?.formValues ?? ctx?.data ?? {}) as Record<string, any>;
179
+ }, [dependentValuesProp, ctx?.formValues, ctx?.data]);
180
+
181
+ /** True when at least one dependency is missing (empty). The picker is gated
182
+ * in that state so we never issue an unfiltered query that ignores the
183
+ * user's earlier choices. */
184
+ const dependenciesMissing = useMemo(() => {
185
+ if (dependsOn.length === 0) return false;
186
+ return dependsOn.some(({ field }) => {
187
+ const v = resolvedDependentValues[field];
188
+ return v === undefined || v === null || v === '' || (Array.isArray(v) && v.length === 0);
189
+ });
190
+ }, [dependsOn, resolvedDependentValues]);
191
+
149
192
  const hasDataSource = dataSource != null && typeof dataSource.find === 'function' && !!referenceTo;
150
193
 
151
194
  // Optional create-new callback
@@ -178,6 +221,12 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
178
221
  const fetchLookupData = useCallback(
179
222
  async (search?: string) => {
180
223
  if (!dataSource || !referenceTo) return;
224
+ // Don't issue a request that ignores configured dependencies.
225
+ if (dependenciesMissing) {
226
+ setFetchedOptions([]);
227
+ setTotalCount(0);
228
+ return;
229
+ }
181
230
 
182
231
  setLoading(true);
183
232
  setError(null);
@@ -190,6 +239,21 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
190
239
  params.$search = search.trim();
191
240
  }
192
241
 
242
+ // Build a dependent-lookup filter chain: AND of `param eq value`.
243
+ // QueryParams.$filter is a Record<string, any> — adapters convert to
244
+ // the underlying query language (OData, ObjectQL, etc).
245
+ if (dependsOn.length > 0) {
246
+ const filterEntries: Record<string, any> = {};
247
+ for (const { field, param } of dependsOn) {
248
+ const v = resolvedDependentValues[field];
249
+ if (v === undefined || v === null || v === '') continue;
250
+ filterEntries[param] = typeof v === 'number' ? v : String(v);
251
+ }
252
+ if (Object.keys(filterEntries).length > 0) {
253
+ params.$filter = filterEntries;
254
+ }
255
+ }
256
+
193
257
  const result = await dataSource.find(referenceTo, params);
194
258
  const records: any[] = result?.data ?? result ?? [];
195
259
  const mapped = records.map(r => recordToOption(r, displayField, idField, descriptionField));
@@ -204,8 +268,23 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
204
268
  setLoading(false);
205
269
  }
206
270
  },
207
- [dataSource, referenceTo, displayField, idField, descriptionField],
271
+ [dataSource, referenceTo, displayField, idField, descriptionField, dependenciesMissing, dependsOn, resolvedDependentValues],
272
+ );
273
+
274
+ // Re-fetch when dependent values change while the picker is open. This keeps
275
+ // a "City" dropdown reactive when the user changes "Country" without closing.
276
+ const dependencySignature = useMemo(
277
+ () => dependsOn.map(d => `${d.param}=${resolvedDependentValues[d.field] ?? ''}`).join('|'),
278
+ [dependsOn, resolvedDependentValues],
208
279
  );
280
+ useEffect(() => {
281
+ if (isOpen && hasDataSource && dependsOn.length > 0) {
282
+ fetchLookupData(searchQuery || undefined);
283
+ // Clear local selection if it no longer satisfies the new filter chain —
284
+ // out of scope here; consumer should validate on submit.
285
+ }
286
+ // eslint-disable-next-line react-hooks/exhaustive-deps
287
+ }, [dependencySignature]);
209
288
 
210
289
  // Fetch data when dialog opens.
211
290
  // We intentionally depend only on `isOpen` so the effect fires once per
@@ -384,17 +463,24 @@ export function LookupField({ value, onChange, field, readonly, ...props }: Fiel
384
463
 
385
464
  {/* Level 1: Quick-select Popover (inline typeahead) */}
386
465
  <div className="flex items-center gap-1.5">
387
- <Popover open={isOpen} onOpenChange={setIsOpen}>
466
+ <Popover open={isOpen} onOpenChange={(o) => !dependenciesMissing && setIsOpen(o)}>
388
467
  <PopoverTrigger asChild>
389
468
  <Button
390
469
  variant="outline"
391
470
  className="min-w-0 flex-1 justify-start text-left font-normal"
392
471
  type="button"
472
+ disabled={dependenciesMissing || (props as any).disabled}
473
+ data-testid={dependenciesMissing ? 'lookup-trigger-gated' : undefined}
474
+ title={dependenciesMissing
475
+ ? `Select ${dependsOn.map(d => d.field).join(', ')} first`
476
+ : undefined}
393
477
  >
394
478
  <Search className="mr-2 size-4" />
395
- {selectedOptions.length === 0
396
- ? lookupField?.placeholder || t('common.select')
397
- : multiple ? t('table.selected', { count: selectedOptions.length }) : t('common.select')
479
+ {dependenciesMissing
480
+ ? `Select ${dependsOn.map(d => d.field).join(', ')} first`
481
+ : selectedOptions.length === 0
482
+ ? lookupField?.placeholder || t('common.select')
483
+ : multiple ? t('table.selected', { count: selectedOptions.length }) : t('common.select')
398
484
  }
399
485
  </Button>
400
486
  </PopoverTrigger>
@@ -1,10 +1,25 @@
1
- import React from 'react';
2
- import { Textarea, EmptyValue } from '@object-ui/components';
1
+ import React, { useState } from 'react';
2
+ import {
3
+ Textarea,
4
+ EmptyValue,
5
+ Dialog,
6
+ DialogContent,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ DialogFooter,
10
+ Button,
11
+ } from '@object-ui/components';
12
+ import { Maximize2, Check, X } from 'lucide-react';
3
13
  import { FieldWidgetProps } from './types';
4
14
 
5
15
  /**
6
16
  * TextAreaField - Multi-line text input widget
7
- * Supports configurable row count and preserves whitespace in readonly mode
17
+ * Supports configurable row count and preserves whitespace in readonly mode.
18
+ *
19
+ * Mobile UX (round 3): when the host form passes `mobileFullscreen` (or the
20
+ * field schema sets `mobile_fullscreen: true`), an "expand" affordance opens
21
+ * a fullscreen edit dialog — much easier on phones than tapping a 4-row
22
+ * textarea trapped between other fields.
8
23
  */
9
24
  export function TextAreaField({ value, onChange, field, readonly, errorMessage, ...props }: FieldWidgetProps<string>) {
10
25
  if (readonly) {
@@ -18,9 +33,23 @@ export function TextAreaField({ value, onChange, field, readonly, errorMessage,
18
33
  const textareaField = (field || (props as any).schema) as any;
19
34
  const rows = textareaField?.rows || 4;
20
35
  const maxLength = textareaField?.max_length;
36
+ // Mobile fullscreen flag may arrive on the field metadata, on the form-field
37
+ // schema (when called via the form renderer where `field` is the ObjectQL
38
+ // metadata sub-object), or as an explicit widget prop.
39
+ const showFullscreenButton = Boolean(
40
+ (props as any).mobileFullscreen ??
41
+ textareaField?.mobile_fullscreen ??
42
+ (props as any).mobile_fullscreen ??
43
+ (props as any).schema?.mobile_fullscreen,
44
+ );
45
+ const [fullscreenOpen, setFullscreenOpen] = useState(false);
46
+ const [draft, setDraft] = useState(value ?? '');
47
+
48
+ const openFullscreen = () => { setDraft(value ?? ''); setFullscreenOpen(true); };
49
+ const cancelFullscreen = () => setFullscreenOpen(false);
50
+ const commitFullscreen = () => { onChange(draft); setFullscreenOpen(false); };
21
51
 
22
- // Filter out non-DOM props
23
- const { inputType, ...domProps } = props as any;
52
+ const { inputType, mobileFullscreen, ...domProps } = props as any;
24
53
 
25
54
  return (
26
55
  <div className="relative">
@@ -33,9 +62,21 @@ export function TextAreaField({ value, onChange, field, readonly, errorMessage,
33
62
  rows={rows}
34
63
  maxLength={maxLength}
35
64
  aria-invalid={!!errorMessage}
65
+ className={domProps.className}
36
66
  />
67
+ {showFullscreenButton && (
68
+ <button
69
+ type="button"
70
+ onClick={openFullscreen}
71
+ className="absolute top-1.5 right-1.5 inline-flex items-center justify-center size-7 rounded-md bg-background/80 text-muted-foreground hover:text-foreground hover:bg-background border shadow-sm transition-colors"
72
+ aria-label={`Edit ${textareaField?.label ?? 'text'} fullscreen`}
73
+ data-testid="textarea-fullscreen-toggle"
74
+ >
75
+ <Maximize2 className="size-3.5" />
76
+ </button>
77
+ )}
37
78
  {maxLength && (
38
- <div
79
+ <div
39
80
  className="absolute bottom-2 right-2 text-xs text-gray-400"
40
81
  aria-live="polite"
41
82
  aria-label={`Character count: ${(value || '').length} of ${maxLength}`}
@@ -43,6 +84,47 @@ export function TextAreaField({ value, onChange, field, readonly, errorMessage,
43
84
  {(value || '').length}/{maxLength}
44
85
  </div>
45
86
  )}
87
+
88
+ {showFullscreenButton && (
89
+ <Dialog open={fullscreenOpen} onOpenChange={setFullscreenOpen}>
90
+ <DialogContent
91
+ className="sm:max-w-3xl h-[100dvh] sm:h-[80vh] max-h-[100dvh] sm:max-h-[80vh] flex flex-col p-0 gap-0"
92
+ data-testid="textarea-fullscreen-dialog"
93
+ >
94
+ <DialogHeader className="p-4 border-b">
95
+ <DialogTitle className="text-base">
96
+ {textareaField?.label ?? 'Edit text'}
97
+ </DialogTitle>
98
+ </DialogHeader>
99
+ <div className="flex-1 min-h-0 p-4">
100
+ <Textarea
101
+ autoFocus
102
+ value={draft}
103
+ onChange={(e) => setDraft(e.target.value)}
104
+ maxLength={maxLength}
105
+ placeholder={textareaField?.placeholder}
106
+ className="h-full min-h-full resize-none text-base"
107
+ data-testid="textarea-fullscreen-input"
108
+ />
109
+ </div>
110
+ <DialogFooter className="p-3 border-t flex-row justify-between sm:justify-end gap-2">
111
+ {maxLength && (
112
+ <span className="text-xs text-muted-foreground self-center">
113
+ {draft.length}/{maxLength}
114
+ </span>
115
+ )}
116
+ <div className="flex gap-2 ml-auto">
117
+ <Button type="button" variant="ghost" onClick={cancelFullscreen}>
118
+ <X className="size-4 mr-1" /> Cancel
119
+ </Button>
120
+ <Button type="button" onClick={commitFullscreen} data-testid="textarea-fullscreen-save">
121
+ <Check className="size-4 mr-1" /> Done
122
+ </Button>
123
+ </div>
124
+ </DialogFooter>
125
+ </DialogContent>
126
+ </Dialog>
127
+ )}
46
128
  </div>
47
129
  );
48
130
  }
@@ -1 +0,0 @@
1
- const e=require(`./chunk-D-qHiVGv.cjs`),t=require(`./useFieldTranslation-BYPwtuNZ.cjs`);let n=require(`react`);n=e.n(n,1);let r=require(`@object-ui/components`),i=require(`lucide-react`),a=require(`react/jsx-runtime`),o=require(`@object-ui/react`);var s=10,ee=60,te=5;function ne(e){return typeof e==`string`?{field:e}:e}function c(e){return e.replace(/_/g,` `).replace(/([a-z])([A-Z])/g,`$1 $2`).replace(/\b\w/g,e=>e.toUpperCase())}function l(e){let t={};for(let n of e)switch(n.operator){case`eq`:t[n.field]=n.value;break;case`ne`:t[n.field]={$ne:n.value};break;case`gt`:t[n.field]={$gt:n.value};break;case`lt`:t[n.field]={$lt:n.value};break;case`gte`:t[n.field]={$gte:n.value};break;case`lte`:t[n.field]={$lte:n.value};break;case`contains`:t[n.field]={$contains:n.value};break;case`in`:t[n.field]={$in:n.value};break;case`notIn`:t[n.field]={$nin:n.value};break}return t}function re(e,t){let n={};for(let r of t){let t=e[r.field];t==null||t===``||(r.type===`boolean`?n[r.field]=!!t:r.type===`text`?n[r.field]={$contains:t}:n[r.field]=t)}return n}function u({open:e,onOpenChange:t,title:o=`Select Record`,multiple:u=!1,dataSource:d,objectName:f,columns:p,displayField:m=`name`,idField:ie=`id`,pageSize:h=s,value:g,onSelect:_,onSelectRecords:v,lookupFilters:y,cellRenderer:b,filterColumns:x,renderFilterBar:S,renderGrid:ae}){let[C,oe]=(0,n.useState)([]),[w,T]=(0,n.useState)(!1),[E,D]=(0,n.useState)(null),[O,k]=(0,n.useState)(0),[A,j]=(0,n.useState)(``),[M,N]=(0,n.useState)(1),P=(0,n.useRef)(null),[F,I]=(0,n.useState)(null),[L,se]=(0,n.useState)(`asc`),[R,ce]=(0,n.useState)(new Set),z=(0,n.useRef)(new Map),[B,V]=(0,n.useState)(-1),H=(0,n.useRef)(null),[le,U]=(0,n.useState)(!1),[W,G]=(0,n.useState)({}),[K,ue]=(0,n.useState)({}),q=(0,n.useRef)(null),[J,Y]=(0,n.useState)(``),X=(0,n.useMemo)(()=>p&&p.length>0?p.map(ne):[{field:m,label:c(m)}],[p,m]),Z=(0,n.useMemo)(()=>{if(x&&x.length>0)return x;if(y&&y.length>0)return y.map(e=>{let t=`text`;return typeof e.value==`boolean`?t=`boolean`:Array.isArray(e.value)?t=`select`:typeof e.value==`number`||e.operator===`gt`||e.operator===`lt`||e.operator===`gte`||e.operator===`lte`?t=`number`:(e.operator===`in`||e.operator===`notIn`)&&(t=`select`),{field:e.field,label:c(e.field),type:t,...Array.isArray(e.value)?{options:e.value.map(e=>{if(typeof e==`object`&&e){let t=e;return{label:String(t.name||t.label||t.title||e),value:e}}return{label:String(e),value:e}})}:{}}})},[x,y]),de=(0,n.useMemo)(()=>{let e=y?.length?l(y):{},t=Z?.length?re(W,Z):{},n={...e,...t};return Object.keys(n).length>0?n:void 0},[y,Z,W]),Q=Math.max(1,Math.ceil(O/h)),fe=(0,n.useCallback)(async(e,t=1,n,r)=>{if(!(!d||!f)){T(!0),D(null);try{let i={$top:h,$skip:(t-1)*h};e&&e.trim()&&(i.$search=e.trim()),n&&(i.$orderby={[n.field]:n.direction});let a=r===void 0?de:r;a&&Object.keys(a).length>0&&(i.$filter=a);let o=await d.find(f,i),s=o?.data??o??[];oe(s),k(o?.total??s.length),V(-1)}catch(e){D(e instanceof Error?e.message:String(e)),oe([])}finally{T(!1)}}},[d,f,h,de]),pe=(0,n.useMemo)(()=>F?{field:F,direction:L}:null,[F,L]);(0,n.useEffect)(()=>{e||(j(``),N(1),D(null),oe([]),I(null),se(`asc`),V(-1),U(!1),G({}),ue({}),Y(``),ce(new Set(u&&Array.isArray(g)?g:[])),z.current.clear())},[e]),(0,n.useEffect)(()=>{e&&fe(A||void 0,M,pe)},[e,M,pe,de]),(0,n.useEffect)(()=>{e&&u&&ce(new Set(Array.isArray(g)?g:[]))},[e,u,g]);let me=(0,n.useCallback)(e=>{j(e),N(1),P.current&&clearTimeout(P.current),P.current=setTimeout(()=>{fe(e||void 0,1,pe)},300)},[fe,pe]);(0,n.useEffect)(()=>()=>{P.current&&clearTimeout(P.current)},[]);let he=(0,n.useCallback)(e=>{I(t=>t===e?(se(e=>e===`asc`?`desc`:`asc`),e):(se(`asc`),e)),N(1)},[]),ge=(0,n.useCallback)(e=>e[ie]??e.id??e._id,[ie]),_e=(0,n.useCallback)(e=>{let t=ge(e);return u?R.has(t):g===t},[u,g,R,ge]),ve=(0,n.useCallback)(e=>{let n=ge(e);u?ce(t=>{let r=new Set(t);return r.has(n)?(r.delete(n),z.current.delete(n)):(r.add(n),z.current.set(n,e)),r}):(_(n),v?.([e]),t(!1))},[u,ge,_,v,t]),ye=(0,n.useCallback)(()=>{let e=Array.from(R);_(e);let n=e.map(e=>z.current.get(e)).filter(Boolean);v?.(n),t(!1)},[R,_,v,t]),be=(0,n.useCallback)(()=>{N(e=>Math.max(1,e-1))},[]),xe=(0,n.useCallback)(()=>{N(e=>Math.min(Q,e+1))},[Q]),Se=(0,n.useCallback)(e=>{if(e.key!==`Enter`)return;let t=parseInt(J,10);!isNaN(t)&&t>=1&&t<=Q&&N(t),Y(``)},[J,Q]),Ce=(0,n.useCallback)(e=>{C.length!==0&&(e.key===`ArrowDown`?(e.preventDefault(),V(e=>Math.min(e+1,C.length-1))):e.key===`ArrowUp`?(e.preventDefault(),V(e=>Math.max(e-1,0))):(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),B>=0&&B<C.length&&ve(C[B])))},[C,B,ve]);(0,n.useEffect)(()=>{if(B>=0&&H.current){let e=H.current.querySelector(`[data-row-index="${B}"]`);e&&typeof e.scrollIntoView==`function`&&e.scrollIntoView({block:`nearest`})}},[B]);let we=(0,n.useCallback)((e,t)=>{let n=e[t.field];if(t.type&&b){let e=b(t.type);if(e)return(0,a.jsx)(e,{value:n,field:{name:t.field,type:t.type}})}return n==null?``:typeof n==`object`?n.$numberDecimal?String(Number(n.$numberDecimal)):n.$oid?String(n.$oid):n.$date?new Date(n.$date).toLocaleDateString():n.name||n.label?String(n.name||n.label):JSON.stringify(n):typeof n==`boolean`?n?`Yes`:`No`:String(n)},[b]),Te=(0,n.useCallback)(e=>F===e?L===`asc`?(0,a.jsx)(i.ArrowUp,{className:`ml-1 size-3`}):(0,a.jsx)(i.ArrowDown,{className:`ml-1 size-3`}):(0,a.jsx)(i.ArrowUpDown,{className:`ml-1 size-3 opacity-40`}),[F,L]),Ee=(0,n.useCallback)((e,t,n)=>{e.preventDefault(),e.stopPropagation(),q.current={field:t,startX:e.clientX,startWidth:n};let r=e=>{if(!q.current)return;let t=e.clientX-q.current.startX,n=Math.max(ee,q.current.startWidth+t);ue(e=>({...e,[q.current.field]:n}))},i=()=>{q.current=null,document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)},[]),$=(0,n.useCallback)((e,t)=>{G(n=>({...n,[e]:t})),N(1)},[]),De=(0,n.useCallback)(()=>{G({}),N(1)},[]),Oe=(0,n.useMemo)(()=>Object.values(W).filter(e=>e!=null&&e!==``).length,[W]),ke=(0,n.useCallback)(e=>{let t=W[e.field],n=e.label||c(e.field);switch(e.type){case`select`:return(0,a.jsxs)(`div`,{className:`space-y-1`,children:[(0,a.jsx)(r.Label,{className:`text-xs text-muted-foreground`,children:n}),(0,a.jsxs)(r.Select,{value:t==null?``:String(t),onValueChange:t=>$(e.field,t),children:[(0,a.jsx)(r.SelectTrigger,{className:`h-8 text-xs`,children:(0,a.jsx)(r.SelectValue,{placeholder:`Filter ${n}`})}),(0,a.jsx)(r.SelectContent,{children:e.options?.map(e=>(0,a.jsx)(r.SelectItem,{value:String(e.value),children:e.label},String(e.value)))})]})]});case`number`:return(0,a.jsxs)(`div`,{className:`space-y-1`,children:[(0,a.jsx)(r.Label,{className:`text-xs text-muted-foreground`,children:n}),(0,a.jsx)(r.Input,{type:`number`,className:`h-8 text-xs`,value:t??``,placeholder:`Filter ${n}`,onChange:t=>{let n=t.target.value;$(e.field,n===``?``:Number(n))}})]});case`date`:return(0,a.jsxs)(`div`,{className:`space-y-1`,children:[(0,a.jsx)(r.Label,{className:`text-xs text-muted-foreground`,children:n}),(0,a.jsx)(r.Input,{type:`date`,className:`h-8 text-xs`,value:t??``,onChange:t=>$(e.field,t.target.value)})]});case`boolean`:return(0,a.jsxs)(`div`,{className:`space-y-1`,children:[(0,a.jsx)(r.Label,{className:`text-xs text-muted-foreground`,children:n}),(0,a.jsxs)(`div`,{className:`flex items-center gap-2 h-8`,children:[(0,a.jsx)(r.Checkbox,{checked:!!t,onCheckedChange:t=>$(e.field,!!t)}),(0,a.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:`Yes`})]})]});default:return(0,a.jsxs)(`div`,{className:`space-y-1`,children:[(0,a.jsx)(r.Label,{className:`text-xs text-muted-foreground`,children:n}),(0,a.jsx)(r.Input,{className:`h-8 text-xs`,value:t??``,placeholder:`Filter ${n}`,onChange:t=>$(e.field,t.target.value)})]})}},[W,$]),Ae=(0,n.useCallback)((e,t)=>e?`bg-primary/5 hover:bg-primary/10`:t%2==1?`bg-muted/20 hover:bg-accent/30`:`hover:bg-accent/30`,[]);return(0,a.jsx)(r.Dialog,{open:e,onOpenChange:t,children:(0,a.jsxs)(r.DialogContent,{className:`w-[95vw] sm:max-w-3xl lg:max-w-5xl max-h-[85vh] sm:max-h-[80vh] flex flex-col gap-0`,"data-testid":`record-picker-dialog`,children:[(0,a.jsx)(r.DialogHeader,{children:(0,a.jsxs)(r.DialogTitle,{children:[o,u&&(0,a.jsx)(`span`,{className:`sr-only`,children:` (multiple selection)`})]})}),(0,a.jsxs)(`div`,{className:`relative rounded-md border bg-muted/30 mb-3`,children:[(0,a.jsx)(i.Search,{className:`absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground`}),(0,a.jsx)(r.Input,{placeholder:`Search...`,value:A,onChange:e=>me(e.target.value),className:`pl-9 border-0 bg-transparent shadow-none focus-visible:ring-0`,"data-testid":`record-picker-search`}),w&&(0,a.jsx)(i.Loader2,{className:`absolute right-3 top-1/2 -translate-y-1/2 size-4 animate-spin text-muted-foreground`,"data-testid":`record-picker-loading-indicator`})]}),Z&&Z.length>0&&(0,a.jsx)(`div`,{className:`py-2`,children:S?(0,a.jsx)(`div`,{"data-testid":`record-picker-filter-bar`,children:S({filterColumns:Z,values:W,onChange:$,onClear:De,activeCount:Oe})}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(`div`,{className:`flex items-center gap-2`,"data-testid":`record-picker-filter-bar`,children:[(0,a.jsxs)(r.Button,{type:`button`,variant:Oe>0?`secondary`:`outline`,size:`sm`,className:`gap-1.5 shrink-0`,onClick:()=>U(e=>!e),children:[(0,a.jsx)(i.SlidersHorizontal,{className:`size-3.5`}),`Filters`,Oe>0&&(0,a.jsx)(`span`,{className:`inline-flex h-5 min-w-[20px] items-center justify-center rounded-full bg-primary/10 px-1 text-xs font-medium text-primary`,children:Oe})]}),Oe>0&&(0,a.jsxs)(r.Button,{type:`button`,variant:`ghost`,size:`sm`,className:`gap-1 text-xs`,onClick:De,children:[(0,a.jsx)(i.X,{className:`size-3`}),`Clear`]})]}),le&&(0,a.jsx)(`div`,{className:`mt-2 grid gap-3 sm:grid-cols-2 lg:grid-cols-3 border rounded-md p-3 bg-muted/30`,"data-testid":`record-picker-filter-panel`,children:Z.map(e=>(0,a.jsx)(`div`,{children:ke(e)},e.field))})]})}),E&&(0,a.jsxs)(`div`,{className:`flex flex-col items-center gap-2 py-4`,role:`alert`,children:[(0,a.jsx)(i.AlertCircle,{className:`size-5 text-destructive`}),(0,a.jsx)(`p`,{className:`text-sm text-destructive`,children:E}),(0,a.jsx)(r.Button,{variant:`outline`,size:`sm`,onClick:()=>fe(A||void 0,M,pe),type:`button`,children:`Retry`})]}),ae?(0,a.jsx)(`div`,{className:`flex-1 min-h-0`,"data-testid":`record-picker-grid-slot`,children:ae({columns:X,records:C,loading:w,totalCount:O,currentPage:M,pageSize:h,sortField:F,sortDirection:L,onSort:he,onPageChange:N,onRowClick:ve,isSelected:_e,multiple:u,idField:ie,cellRenderer:b})}):(0,a.jsxs)(a.Fragment,{children:[w&&C.length===0&&!E&&(0,a.jsx)(`div`,{className:`flex-1 overflow-hidden min-h-0 border rounded-md`,role:`status`,"aria-live":`polite`,"data-testid":`record-picker-skeleton`,children:(0,a.jsxs)(r.Table,{children:[(0,a.jsx)(r.TableHeader,{children:(0,a.jsxs)(r.TableRow,{className:`bg-muted/40`,children:[u&&(0,a.jsx)(r.TableHead,{className:`w-10`}),X.map(e=>(0,a.jsx)(r.TableHead,{children:(0,a.jsx)(r.Skeleton,{className:`h-4 w-20`})},e.field))]})}),(0,a.jsx)(r.TableBody,{children:Array.from({length:te},(e,t)=>(0,a.jsxs)(r.TableRow,{children:[u&&(0,a.jsx)(r.TableCell,{className:`w-10`,children:(0,a.jsx)(r.Skeleton,{className:`size-4 rounded`})}),X.map(e=>(0,a.jsx)(r.TableCell,{children:(0,a.jsx)(r.Skeleton,{className:`h-4 w-full`})},e.field))]},t))})]})}),!w&&!E&&C.length===0&&(0,a.jsx)(`div`,{className:`py-8 text-center`,children:(0,a.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`No records found`})}),!E&&C.length>0&&(0,a.jsxs)(`div`,{className:`relative flex-1 overflow-auto min-h-0 border rounded-md`,tabIndex:0,onKeyDown:Ce,role:`grid`,"aria-label":`Records`,children:[w&&(0,a.jsx)(`div`,{className:`absolute inset-0 z-10 flex items-center justify-center bg-background/60`,"data-testid":`record-picker-loading-overlay`,children:(0,a.jsx)(i.Loader2,{className:`size-6 animate-spin text-muted-foreground`})}),(0,a.jsxs)(r.Table,{style:Object.keys(K).length>0?{tableLayout:`fixed`}:void 0,children:[(0,a.jsx)(r.TableHeader,{className:`sticky top-0 z-[5] bg-muted/50 [&_tr]:border-b`,"data-testid":`record-picker-sticky-header`,children:(0,a.jsxs)(r.TableRow,{children:[u&&(0,a.jsx)(r.TableHead,{className:`w-10`}),X.map(e=>{let t=K[e.field];return(0,a.jsxs)(r.TableHead,{style:t?{width:`${t}px`,minWidth:`${t}px`}:e.width?{width:e.width}:void 0,className:`cursor-pointer select-none relative group text-xs font-semibold uppercase tracking-wider`,onClick:()=>he(e.field),"aria-sort":F===e.field?L===`asc`?`ascending`:`descending`:`none`,children:[(0,a.jsxs)(`span`,{className:`inline-flex items-center`,children:[e.label||c(e.field),Te(e.field)]}),(0,a.jsx)(`span`,{role:`separator`,"aria-orientation":`vertical`,className:`absolute right-0 top-0 bottom-0 w-1 cursor-col-resize opacity-0 group-hover:opacity-100 bg-border hover:bg-primary/50 transition-opacity`,onMouseDown:t=>{let n=t.currentTarget.parentElement?.getBoundingClientRect();Ee(t,e.field,n?.width??100)},onClick:e=>e.stopPropagation(),"data-testid":`resize-handle-${e.field}`})]},e.field)})]})}),(0,a.jsx)(r.TableBody,{ref:H,children:C.map((e,t)=>{let n=ge(e),o=_e(e),s=t===B;return(0,a.jsxs)(r.TableRow,{"data-row-index":t,className:(0,r.cn)(`cursor-pointer transition-colors`,Ae(o,t),s&&`ring-2 ring-primary ring-inset`),onClick:()=>ve(e),"data-testid":`record-row-${n}`,"aria-selected":o,children:[u&&(0,a.jsx)(r.TableCell,{className:`w-10`,children:o&&(0,a.jsx)(i.Check,{className:`size-4 text-primary`})}),X.map(t=>(0,a.jsx)(r.TableCell,{className:`py-2.5`,children:we(e,t)},t.field))]},n??t)})})]})]}),!E&&O>0&&(0,a.jsxs)(`div`,{className:`flex items-center justify-between text-sm text-muted-foreground border-t pt-3 mt-2 px-1`,"data-testid":`record-picker-pagination`,children:[(0,a.jsxs)(`span`,{children:[O,` `,O===1?`record`:`records`,Q>1&&` · Page ${M} of ${Q}`]}),Q>1&&(0,a.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,a.jsx)(r.Button,{variant:`outline`,size:`icon`,className:`size-7`,onClick:be,disabled:M<=1,type:`button`,"aria-label":`Previous page`,children:(0,a.jsx)(i.ChevronLeft,{className:`size-4`})}),(0,a.jsx)(r.Input,{className:`h-7 w-12 text-center text-xs px-1`,placeholder:String(M),value:J,onChange:e=>Y(e.target.value),onKeyDown:Se,"aria-label":`Jump to page`,"data-testid":`record-picker-page-jump`}),(0,a.jsx)(r.Button,{variant:`outline`,size:`icon`,className:`size-7`,onClick:xe,disabled:M>=Q,type:`button`,"aria-label":`Next page`,children:(0,a.jsx)(i.ChevronRight,{className:`size-4`})})]})]})]}),u&&(0,a.jsx)(r.DialogFooter,{children:(0,a.jsxs)(`div`,{className:`flex items-center gap-2 w-full justify-between`,children:[(0,a.jsxs)(`span`,{className:`text-sm text-muted-foreground`,children:[R.size,` selected`]}),(0,a.jsxs)(`div`,{className:`flex gap-2`,children:[(0,a.jsx)(r.Button,{variant:`outline`,type:`button`,onClick:()=>t(!1),children:`Cancel`}),(0,a.jsx)(r.Button,{type:`button`,onClick:ye,children:`Confirm`})]})]})})]})})}var d;function f(e){d=e}function p(){return d}var m=e.t({LookupField:()=>v}),ie=50,h=o.SchemaRendererContext;function g(e,t,n,r){let i=e[n]??e.id??e._id,a=e[t]??e.label??e.name??String(i),o=r?e[r]:void 0;return{value:i,label:String(a),description:o,...e}}function _(e){return{text:`text`,number:`number`,currency:`number`,percent:`number`,select:`select`,status:`select`,date:`date`,datetime:`date`,boolean:`boolean`}[e]}function v({value:e,onChange:o,field:s,readonly:ee,...te}){let[ne,c]=(0,n.useState)(!1),[l,re]=(0,n.useState)(``),{t:d}=t.t(),[f,m]=(0,n.useState)([]),[v,y]=(0,n.useState)(!1),[b,x]=(0,n.useState)(null),[S,ae]=(0,n.useState)(0),C=(0,n.useRef)(null),[oe,w]=(0,n.useState)([]),[T,E]=(0,n.useState)(-1),D=(0,n.useRef)(null),O=s||te.schema,k=O?.field,A=k&&typeof k==`object`&&(`reference_to`in k||`reference`in k||`type`in k)?k:O,j=A?.options||[],M=A?.multiple||!1,N=A?.display_field||A?.reference_field||`name`,P=A?.description_field,F=A?.id_field||`id`,I=A?.reference_to||A?.reference,L=A?.lookup_columns,se=A?.lookup_page_size,R=A?.lookup_filters,ce=(0,n.useMemo)(()=>{if(!L)return;let e=[];for(let t of L)if(typeof t==`object`&&t.type){let n=_(t.type);n&&e.push({field:t.field,label:t.label,type:n})}return e.length>0?e:void 0},[L]),z=(0,n.useContext)(h)?.dataSource??null,B=te.dataSource??O?.dataSource??A?.dataSource??z,V=B!=null&&typeof B.find==`function`&&!!I,H=te.onCreateNew??O?.onCreateNew,[le,U]=(0,n.useState)(!1),W=V?f:j,G=(0,n.useMemo)(()=>{if(V||!l)return W;let e=l.toLowerCase();return W.filter(t=>t.label.toLowerCase().includes(e)||t.description&&t.description.toLowerCase().includes(e))},[V,W,l]);(0,n.useEffect)(()=>{E(-1)},[G.length]);let K=(0,n.useCallback)(async e=>{if(!(!B||!I)){y(!0),x(null);try{let t={$top:ie};e&&e.trim()&&(t.$search=e.trim());let n=await B.find(I,t),r=n?.data??n??[];m(r.map(e=>g(e,N,F,P))),ae(n?.total??r.length)}catch(e){x(e instanceof Error?e.message:String(e)),m([])}finally{y(!1)}}},[B,I,N,F,P]);(0,n.useEffect)(()=>{ne&&V&&K(l||void 0),ne||(re(``),x(null),E(-1))},[ne]);let ue=(0,n.useCallback)(e=>{re(e),V&&(C.current&&clearTimeout(C.current),C.current=setTimeout(()=>{K(e||void 0)},300))},[V,K]);(0,n.useEffect)(()=>()=>{C.current&&clearTimeout(C.current)},[]);let q=(0,n.useCallback)(e=>j.find(t=>t.value===e)??f.find(t=>t.value===e)??oe.find(t=>t.value===e),[j,f,oe]),J=M?(Array.isArray(e)?e:[]).map(q).filter(Boolean):e?[q(e)].filter(Boolean):[],Y=(0,n.useCallback)(t=>{if(M){let n=Array.isArray(e)?e:[];n.includes(t.value)?o(n.filter(e=>e!==t.value)):o([...n,t.value])}else o(t.value),c(!1)},[M,e,o]),X=t=>{o(M?(Array.isArray(e)?e:[]).filter(e=>e!==t):null)},Z=(0,n.useCallback)(e=>{w(e.map(e=>g(e,N,F,P)))},[N,F,P]),de=(0,n.useCallback)(e=>{e.key===`ArrowDown`?(e.preventDefault(),E(e=>e<G.length-1?e+1:e)):e.key===`ArrowUp`?(e.preventDefault(),E(e=>e>0?e-1:0)):e.key===`Enter`&&(e.preventDefault(),T>=0&&T<G.length&&Y(G[T]))},[G,T,Y]);return(0,n.useEffect)(()=>{T>=0&&D.current&&D.current.querySelector(`[data-lookup-index="${T}"]`)?.scrollIntoView({block:`nearest`})},[T]),ee?J.length?M?(0,a.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:J.map((e,t)=>(0,a.jsx)(r.Badge,{variant:`outline`,children:e?.[N]||e?.label},t))}):(0,a.jsx)(`span`,{className:`text-sm`,children:J[0]?.[N]||J[0]?.label}):(0,a.jsx)(r.EmptyValue,{}):(0,a.jsxs)(`div`,{className:`space-y-2`,children:[J.length>0&&(0,a.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:J.map((e,t)=>(0,a.jsxs)(r.Badge,{variant:`outline`,className:`gap-1`,children:[e?.[N]||e?.label,(0,a.jsx)(`button`,{onClick:()=>X(e?.value),className:`ml-1 hover:text-destructive`,type:`button`,"aria-label":`Remove ${e?.[N]||e?.label}`,children:(0,a.jsx)(i.X,{className:`size-3`})})]},t))}),(0,a.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,a.jsxs)(r.Popover,{open:ne,onOpenChange:c,children:[(0,a.jsx)(r.PopoverTrigger,{asChild:!0,children:(0,a.jsxs)(r.Button,{variant:`outline`,className:`min-w-0 flex-1 justify-start text-left font-normal`,type:`button`,children:[(0,a.jsx)(i.Search,{className:`mr-2 size-4`}),J.length===0?O?.placeholder||d(`common.select`):M?d(`table.selected`,{count:J.length}):d(`common.select`)]})}),(0,a.jsxs)(r.PopoverContent,{className:`w-[var(--radix-popover-trigger-width)] p-0`,align:`start`,children:[(0,a.jsx)(`div`,{className:`p-2`,children:(0,a.jsxs)(`div`,{className:`relative`,children:[(0,a.jsx)(i.Search,{className:`absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground`}),(0,a.jsx)(r.Input,{placeholder:d(`common.search`)+`...`,value:l,onChange:e=>ue(e.target.value),onKeyDown:de,className:`w-full pl-9 h-8 text-sm`}),v&&(0,a.jsx)(i.Loader2,{className:`absolute right-3 top-1/2 -translate-y-1/2 size-4 animate-spin text-muted-foreground`,"data-testid":`lookup-loading`})]})}),b&&(0,a.jsxs)(`div`,{className:`flex flex-col items-center gap-2 py-4 px-2`,role:`alert`,children:[(0,a.jsx)(i.AlertCircle,{className:`size-5 text-destructive`}),(0,a.jsx)(`p`,{className:`text-sm text-destructive`,children:b}),(0,a.jsx)(r.Button,{variant:`outline`,size:`sm`,onClick:()=>K(l||void 0),type:`button`,children:`Retry`})]}),v&&G.length===0&&!b&&(0,a.jsxs)(`div`,{className:`flex flex-col items-center gap-2 py-6`,role:`status`,"aria-live":`polite`,children:[(0,a.jsx)(i.Loader2,{className:`size-6 animate-spin text-muted-foreground`}),(0,a.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Loading…`})]}),!b&&!(v&&G.length===0)&&(0,a.jsx)(`div`,{ref:D,className:`max-h-64 overflow-y-auto px-1 pb-1`,role:`listbox`,children:G.length===0?(0,a.jsxs)(`div`,{className:`py-4 text-center`,children:[(0,a.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`No options found`}),H&&(0,a.jsxs)(r.Button,{variant:`ghost`,size:`sm`,className:`mt-2 gap-1`,type:`button`,onClick:()=>{H(l),c(!1)},children:[(0,a.jsx)(i.Plus,{className:`size-4`}),`Create new`]})]}):(0,a.jsxs)(a.Fragment,{children:[G.map((t,n)=>{let i=M?(Array.isArray(e)?e:[]).includes(t.value):e===t.value;return(0,a.jsxs)(`button`,{"data-lookup-index":n,role:`option`,"aria-selected":i,onClick:()=>Y(t),className:`w-full text-left px-3 py-2 rounded-md text-sm hover:bg-accent flex items-center justify-between ${n===T?`bg-accent text-accent-foreground`:i?`bg-accent/50 text-accent-foreground`:``}`,type:`button`,children:[(0,a.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,a.jsx)(`span`,{className:`block truncate`,children:t.label}),t.description&&(0,a.jsx)(`span`,{className:`block truncate text-xs text-muted-foreground`,children:t.description})]}),i&&(0,a.jsx)(r.Badge,{variant:`default`,className:`ml-2 shrink-0`,children:`Selected`})]},t.value)}),V&&S>G.length&&(0,a.jsxs)(`p`,{className:`text-xs text-muted-foreground text-center py-2`,children:[`Showing `,G.length,` of `,S,` results.`]}),V&&S>G.length&&(0,a.jsxs)(`button`,{type:`button`,className:`w-full text-center px-3 py-2 rounded-md text-sm font-medium text-primary hover:bg-accent flex items-center justify-center gap-1.5`,onClick:()=>{c(!1),U(!0)},"data-testid":`show-all-results`,children:[(0,a.jsx)(i.TableProperties,{className:`size-3.5`}),`Show All Results (`,S,`)`]}),H&&(0,a.jsxs)(`button`,{type:`button`,className:`w-full text-left px-3 py-2 rounded-md text-sm hover:bg-accent flex items-center gap-1.5 text-muted-foreground`,onClick:()=>{H(l),c(!1)},children:[(0,a.jsx)(i.Plus,{className:`size-3.5`}),`Create new`,l?` "${l}"`:``]})]})})]})]}),V&&(0,a.jsx)(r.Button,{variant:`outline`,size:`icon`,className:`shrink-0`,type:`button`,onClick:()=>U(!0),"aria-label":`Browse all records`,title:`Browse all records`,"data-testid":`browse-all-records`,children:(0,a.jsx)(i.TableProperties,{className:`size-4`})})]}),V&&B&&I&&(0,a.jsx)(u,{open:le,onOpenChange:U,title:O?.label||`Select`,multiple:M,dataSource:B,objectName:I,columns:L,displayField:N,idField:F,pageSize:se,value:e,onSelect:o,onSelectRecords:Z,lookupFilters:R,cellRenderer:p(),filterColumns:ce})]})}Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return v}});