@asteby/metacore-runtime-react 49.7.0 → 49.9.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/business/document-editor-model.d.ts +27 -2
  3. package/dist/business/document-editor-model.d.ts.map +1 -1
  4. package/dist/business/document-editor-model.js +53 -3
  5. package/dist/business/document-editor.d.ts +7 -2
  6. package/dist/business/document-editor.d.ts.map +1 -1
  7. package/dist/business/document-editor.js +121 -22
  8. package/dist/business/document-lines.d.ts +21 -1
  9. package/dist/business/document-lines.d.ts.map +1 -1
  10. package/dist/business/document-lines.js +69 -7
  11. package/dist/business/index.d.ts +1 -0
  12. package/dist/business/index.d.ts.map +1 -1
  13. package/dist/business/index.js +1 -0
  14. package/dist/business/line-items-editor.d.ts +21 -1
  15. package/dist/business/line-items-editor.d.ts.map +1 -1
  16. package/dist/business/line-items-editor.js +74 -15
  17. package/dist/business/line-items.d.ts +28 -0
  18. package/dist/business/line-items.d.ts.map +1 -1
  19. package/dist/business/line-items.js +1 -0
  20. package/dist/business/line-product-cell.d.ts +6 -0
  21. package/dist/business/line-product-cell.d.ts.map +1 -1
  22. package/dist/business/line-product-cell.js +9 -3
  23. package/dist/business/product-display.d.ts +18 -0
  24. package/dist/business/product-display.d.ts.map +1 -0
  25. package/dist/business/product-display.js +40 -0
  26. package/dist/business/product-options.d.ts +6 -1
  27. package/dist/business/product-options.d.ts.map +1 -1
  28. package/dist/business/product-options.js +11 -2
  29. package/dist/business/product-search.d.ts +8 -0
  30. package/dist/business/product-search.d.ts.map +1 -1
  31. package/dist/dialogs/dynamic-record.js +1 -1
  32. package/dist/dynamic-select-field.d.ts +7 -1
  33. package/dist/dynamic-select-field.d.ts.map +1 -1
  34. package/dist/dynamic-select-field.js +18 -4
  35. package/dist/field-grid.d.ts +4 -3
  36. package/dist/field-grid.d.ts.map +1 -1
  37. package/dist/field-grid.js +1 -1
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1 -0
  41. package/dist/option-display.d.ts +68 -0
  42. package/dist/option-display.d.ts.map +1 -0
  43. package/dist/option-display.js +135 -0
  44. package/dist/query-batch.d.ts +1 -1
  45. package/dist/query-batch.d.ts.map +1 -1
  46. package/dist/query-batch.js +3 -1
  47. package/dist/record-picker.d.ts +2 -0
  48. package/dist/record-picker.d.ts.map +1 -1
  49. package/dist/record-picker.js +11 -4
  50. package/dist/use-options-resolver.d.ts +16 -1
  51. package/dist/use-options-resolver.d.ts.map +1 -1
  52. package/dist/use-options-resolver.js +38 -6
  53. package/package.json +1 -1
  54. package/src/__tests__/document-editor-inline-selects.test.tsx +11 -4
  55. package/src/__tests__/line-row-create-edit.test.tsx +312 -0
  56. package/src/__tests__/option-display.test.tsx +241 -0
  57. package/src/business/document-editor-model.ts +60 -2
  58. package/src/business/document-editor.tsx +143 -19
  59. package/src/business/document-lines.ts +79 -7
  60. package/src/business/index.ts +1 -0
  61. package/src/business/line-items-editor.tsx +158 -30
  62. package/src/business/line-items.ts +31 -0
  63. package/src/business/line-product-cell.tsx +32 -5
  64. package/src/business/product-display.ts +62 -0
  65. package/src/business/product-options.tsx +11 -2
  66. package/src/business/product-search.ts +8 -0
  67. package/src/dialogs/dynamic-record.tsx +1 -1
  68. package/src/dynamic-select-field.tsx +45 -1
  69. package/src/field-grid.tsx +5 -4
  70. package/src/index.ts +10 -0
  71. package/src/option-display.tsx +255 -0
  72. package/src/query-batch.ts +2 -0
  73. package/src/record-picker.tsx +16 -3
  74. package/src/use-options-resolver.ts +49 -4
@@ -268,6 +268,8 @@ export interface RecordPickerProps<T> {
268
268
  inputRef?: Ref<HTMLInputElement>
269
269
  /** Floor for the list width (CSS length). Default 14rem. */
270
270
  minListWidth?: string
271
+ /** Ceiling for the list width (CSS length), e.g. under a very wide trigger. Default: none. */
272
+ maxListWidth?: string
271
273
  /** Rendered after the joined group (e.g. a scan button). */
272
274
  after?: ReactNode
273
275
  /** Rendered under the control (badges, hints). */
@@ -351,6 +353,7 @@ export function RecordPicker<T>(props: RecordPickerProps<T>) {
351
353
  triggerProps,
352
354
  inputRef,
353
355
  minListWidth = '14rem',
356
+ maxListWidth,
354
357
  after,
355
358
  below,
356
359
  beforeList,
@@ -555,7 +558,15 @@ export function RecordPicker<T>(props: RecordPickerProps<T>) {
555
558
  )
556
559
 
557
560
  const list = (
558
- <div className="max-h-72 overflow-y-auto overflow-x-hidden overscroll-contain" data-slot="record-picker-list">
561
+ <div
562
+ className="overflow-y-auto overflow-x-hidden overscroll-contain"
563
+ data-slot="record-picker-list"
564
+ // Never taller than the room Radix measured on the side it opened
565
+ // (flipped above the field near the bottom of a modal it used to
566
+ // spill past the top of the viewport): 18rem at most, minus the
567
+ // search box in button mode; the list scrolls inside.
568
+ style={{ maxHeight: `min(18rem, calc(var(--radix-popover-content-available-height, 18rem) - ${isInput ? '2px' : '2.75rem'}))` }}
569
+ >
559
570
  {beforeList}
560
571
  {status}
561
572
  <ul
@@ -596,7 +607,7 @@ export function RecordPicker<T>(props: RecordPickerProps<T>) {
596
607
  ) : null}
597
608
  {renderLead?.(item, 'option')}
598
609
  <span className="flex min-w-0 flex-1 flex-col">
599
- <span className="truncate">{getLabel(item)}</span>
610
+ <span className="line-clamp-2 break-words">{getLabel(item)}</span>
600
611
  {desc != null && desc !== '' ? (
601
612
  <span className="truncate text-xs text-muted-foreground">{desc}</span>
602
613
  ) : null}
@@ -636,7 +647,9 @@ export function RecordPicker<T>(props: RecordPickerProps<T>) {
636
647
  )
637
648
 
638
649
  const contentStyle = {
639
- width: `max(var(--radix-popover-trigger-width), ${minListWidth})`,
650
+ width: maxListWidth
651
+ ? `min(max(var(--radix-popover-trigger-width), ${minListWidth}), ${maxListWidth})`
652
+ : `max(var(--radix-popover-trigger-width), ${minListWidth})`,
640
653
  maxWidth: 'calc(100vw - 1rem)',
641
654
  zIndex: RECORD_PICKER_Z_INDEX,
642
655
  }
@@ -14,6 +14,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
14
14
  import { useApi } from './api-context'
15
15
  import { applyOptionFilter, type OptionFilterRule } from './option-filter'
16
16
  import { loadQueryPart, optionsBatchToken, optionsModelFromUrl } from './query-batch'
17
+ import type { OptionDisplayData } from './option-display'
17
18
 
18
19
  export interface ResolvedOption {
19
20
  /** Canonical id (server-side primary key). */
@@ -28,6 +29,12 @@ export interface ResolvedOption {
28
29
  image?: string | null
29
30
  color?: string | null
30
31
  icon?: string | null
32
+ /**
33
+ * Presentación declarativa resuelta por el kernel (manifest v3
34
+ * `option_display`): título, subtítulo, imagen, métricas a la derecha con
35
+ * tono (precio, stock…) y badges. Ausente en hosts/modelos sin display.
36
+ */
37
+ display?: OptionDisplayData | null
31
38
  /**
32
39
  * Campos extra del payload (precio, costo, sku, tasa) que no son decoración.
33
40
  * Solo se llena cuando el option trae números o strings además de id/label.
@@ -98,6 +105,12 @@ export interface UseOptionsResolverArgs {
98
105
  optionFilter?: OptionFilterRule[]
99
106
  /** Current selection: never hidden by `optionFilter`, so its label survives. */
100
107
  keepValue?: unknown
108
+ /**
109
+ * Picker context for the option display's contributed metrics, sent as
110
+ * `?ctx.<key>=<value>` (e.g. `{ warehouse_id }` of the document being
111
+ * edited → stock in that warehouse). Empty values are dropped.
112
+ */
113
+ context?: Record<string, string | null | undefined>
101
114
  }
102
115
 
103
116
  export interface UseOptionsResolverResult {
@@ -157,8 +170,34 @@ export function optionsRequestKey(
157
170
  query: string,
158
171
  limit: number | undefined,
159
172
  filter: string | undefined,
173
+ context?: Record<string, string>,
160
174
  ): string {
161
- return [scope, url, field, query, String(limit ?? ''), filter ?? ''].join('\n')
175
+ const parts = [scope, url, field, query, String(limit ?? ''), filter ?? '']
176
+ const ctx = contextKey(context)
177
+ if (ctx) parts.push(ctx)
178
+ return parts.join('\n')
179
+ }
180
+
181
+ /** Normalized picker context: string values only, empty dropped (null when none). */
182
+ export function normalizeOptionsContext(
183
+ context: Record<string, string | null | undefined> | undefined,
184
+ ): Record<string, string> | undefined {
185
+ if (!context) return undefined
186
+ const out: Record<string, string> = {}
187
+ for (const [k, v] of Object.entries(context)) {
188
+ if (v == null) continue
189
+ const s = String(v).trim()
190
+ if (s) out[k] = s
191
+ }
192
+ return Object.keys(out).length > 0 ? out : undefined
193
+ }
194
+
195
+ function contextKey(context: Record<string, string> | undefined): string {
196
+ if (!context) return ''
197
+ return Object.keys(context)
198
+ .sort()
199
+ .map((k) => `${k}=${context[k]}`)
200
+ .join('&')
162
201
  }
163
202
 
164
203
  /** Org + branch of the host session: part of every options cache key. */
@@ -203,6 +242,8 @@ export function useOptionsResolver(args: UseOptionsResolverArgs): UseOptionsReso
203
242
  optionFilter,
204
243
  keepValue,
205
244
  } = args
245
+ const context = normalizeOptionsContext(args.context)
246
+ const ctxSig = contextKey(context)
206
247
 
207
248
  const api = useApi()
208
249
  const [options, setOptions] = useState<ResolvedOption[]>([])
@@ -251,6 +292,7 @@ export function useOptionsResolver(args: UseOptionsResolverArgs): UseOptionsReso
251
292
  query ?? '',
252
293
  limit,
253
294
  filterValue,
295
+ context,
254
296
  )
255
297
  if (refreshKey !== seenRefresh.current) {
256
298
  seenRefresh.current = refreshKey
@@ -278,13 +320,14 @@ export function useOptionsResolver(args: UseOptionsResolverArgs): UseOptionsReso
278
320
  if (query) params.q = query
279
321
  if (typeof limit === 'number' && limit > 0) params.limit = limit
280
322
  if (filterValue) params.filter_value = filterValue
323
+ for (const [k, v] of Object.entries(context ?? {})) params[`ctx.${k}`] = v
281
324
  const model = optionsModelFromUrl(url)
282
325
  pending = (async () => {
283
326
  if (model) {
284
327
  try {
285
328
  const part = await loadQueryPart(
286
329
  api,
287
- optionsBatchToken(model, effectiveField, query ?? '', limit, filterValue),
330
+ optionsBatchToken(model, effectiveField, query ?? '', limit, filterValue, context),
288
331
  )
289
332
  if (!part.success) {
290
333
  throw new Error(part.message || 'options resolver: unsuccessful response')
@@ -340,7 +383,8 @@ export function useOptionsResolver(args: UseOptionsResolverArgs): UseOptionsReso
340
383
  return () => {
341
384
  cancelled = true
342
385
  }
343
- }, [api, url, effectiveField, query, limit, enabled, filterValue, refreshKey])
386
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- context is keyed by ctxSig
387
+ }, [api, url, effectiveField, query, limit, enabled, filterValue, refreshKey, ctxSig])
344
388
 
345
389
  // Rules arrive as a fresh array each render; key on their content.
346
390
  const filterKey = optionFilter && optionFilter.length > 0 ? JSON.stringify(optionFilter) : ''
@@ -364,7 +408,7 @@ export function useOptionsResolver(args: UseOptionsResolverArgs): UseOptionsReso
364
408
  * id/value and label/name fields for legacy parity — we accept either
365
409
  * and surface a stable shape downstream.
366
410
  */
367
- const OPTION_KNOWN_KEYS = new Set(['id', 'value', 'label', 'name', 'description', 'image', 'color', 'icon'])
411
+ const OPTION_KNOWN_KEYS = new Set(['id', 'value', 'label', 'name', 'description', 'image', 'color', 'icon', 'display'])
368
412
 
369
413
  export function projectOption(raw: any): ResolvedOption {
370
414
  const id = raw?.id ?? raw?.value ?? ''
@@ -385,6 +429,7 @@ export function projectOption(raw: any): ResolvedOption {
385
429
  image: raw?.image ?? null,
386
430
  color: raw?.color ?? null,
387
431
  icon: raw?.icon ?? null,
432
+ ...(raw?.display && typeof raw.display === 'object' ? { display: raw.display as OptionDisplayData } : {}),
388
433
  ...(Object.keys(meta).length > 0 ? { meta } : {}),
389
434
  }
390
435
  }