@object-ui/plugin-detail 3.1.2 → 3.1.3

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 (61) hide show
  1. package/.turbo/turbo-build.log +43 -41
  2. package/CHANGELOG.md +10 -0
  3. package/dist/{AddressField-QBIlXCFl.js → AddressField-BtiTrEpf.js} +1 -1
  4. package/dist/{AvatarField-BEZuQTAH.js → AvatarField-CwlnWNSf.js} +7 -7
  5. package/dist/{BooleanField-doa93aFX.js → BooleanField-DpMXU2ya.js} +1 -1
  6. package/dist/{CodeField-jVV-hIXg.js → CodeField-gwmcFihg.js} +2 -2
  7. package/dist/{ColorField-B53qKQGW.js → ColorField-CWmF_zoW.js} +1 -1
  8. package/dist/{CurrencyField-og0NJ2ax.js → CurrencyField-BF3tYAgm.js} +1 -1
  9. package/dist/{DateField-BFx64AtG.js → DateField-a6Ka9ph2.js} +1 -1
  10. package/dist/{DateTimeField-Cxs2Rx2f.js → DateTimeField-C4wWOEiw.js} +1 -1
  11. package/dist/{EmailField-BfcpzRe7.js → EmailField-DJqiQ4sp.js} +1 -1
  12. package/dist/{FileField-KarqvhYm.js → FileField-ChjjCydz.js} +1 -1
  13. package/dist/{GeolocationField-B5SKZaqn.js → GeolocationField-BnkeUBek.js} +1 -1
  14. package/dist/{GridField-DOotrUTo.js → GridField-DoHqc2ON.js} +6 -6
  15. package/dist/{ImageField-Ddotp4u-.js → ImageField-Ld7SHA8N.js} +1 -1
  16. package/dist/{LocationField-tOkQaPIM.js → LocationField-Bgu-vMAE.js} +1 -1
  17. package/dist/{MasterDetailField-CpHw3nTE.js → MasterDetailField-Bp5WBTzU.js} +3 -3
  18. package/dist/{NumberField-CzBb2a28.js → NumberField-uBqVZ-gt.js} +1 -1
  19. package/dist/{ObjectField-BoL-JqE4.js → ObjectField-BH1Md9gH.js} +6 -6
  20. package/dist/{PasswordField-DrTzkYgj.js → PasswordField-D8GZjY7d.js} +1 -1
  21. package/dist/{PercentField-B9ZUQ3zE.js → PercentField-DyK8vg8M.js} +1 -1
  22. package/dist/{PhoneField-Bf9lhpdu.js → PhoneField-B3qJyLP0.js} +1 -1
  23. package/dist/{QRCodeField-PzMpdBKd.js → QRCodeField-CGiRTCZq.js} +1 -1
  24. package/dist/{RatingField-CeBMFe8o.js → RatingField-CWVaJNyf.js} +4 -4
  25. package/dist/{RichTextField-Ch7CHSQ0.js → RichTextField-CusveP9T.js} +1 -1
  26. package/dist/{SelectField-f5Nbi02x.js → SelectField-UdDfsEZo.js} +1 -1
  27. package/dist/{SignatureField-CpxTX2tR.js → SignatureField-DFvPKbuI.js} +1 -1
  28. package/dist/{SliderField-BoZtzgcr.js → SliderField-C-HvGV9e.js} +1 -1
  29. package/dist/{TextAreaField-rT1DLnV2.js → TextAreaField-C5KygUT3.js} +1 -1
  30. package/dist/{TextField-CflRxusu.js → TextField-oUjuqQ1x.js} +1 -1
  31. package/dist/{TimeField-DeVeCpRu.js → TimeField-SsQ6rfk5.js} +1 -1
  32. package/dist/{UrlField-UWKfhP9T.js → UrlField-kd48Ip95.js} +1 -1
  33. package/dist/{UserField-Cp2zQDjz.js → UserField-BOjE_CAz.js} +11 -11
  34. package/dist/{index-V_WBvcaA.js → index-D2t9pLAg.js} +56341 -56642
  35. package/dist/index.js +10 -10
  36. package/dist/index.umd.cjs +61 -63
  37. package/dist/plugin-detail.css +1 -1
  38. package/dist/src/DetailSection.d.ts +10 -0
  39. package/dist/src/DetailSection.d.ts.map +1 -1
  40. package/dist/src/HeaderHighlight.d.ts +2 -0
  41. package/dist/src/HeaderHighlight.d.ts.map +1 -1
  42. package/dist/src/RecordChatterPanel.d.ts +2 -0
  43. package/dist/src/RecordChatterPanel.d.ts.map +1 -1
  44. package/dist/src/autoLayout.d.ts +10 -3
  45. package/dist/src/autoLayout.d.ts.map +1 -1
  46. package/dist/src/index.d.ts +1 -1
  47. package/dist/src/index.d.ts.map +1 -1
  48. package/package.json +7 -7
  49. package/src/DetailSection.tsx +40 -1
  50. package/src/DetailView.tsx +1 -1
  51. package/src/HeaderHighlight.tsx +22 -1
  52. package/src/RecordChatterPanel.tsx +6 -1
  53. package/src/RelatedList.tsx +1 -1
  54. package/src/__tests__/DetailSection.test.tsx +61 -0
  55. package/src/__tests__/HeaderHighlight.test.tsx +145 -0
  56. package/src/__tests__/RecordChatterPanel.test.tsx +38 -0
  57. package/src/__tests__/RelatedList.test.tsx +3 -3
  58. package/src/__tests__/autoLayout.test.ts +44 -0
  59. package/src/autoLayout.ts +25 -8
  60. package/src/index.tsx +1 -1
  61. package/dist/LookupField-DF36GvIP.js +0 -96
@@ -37,6 +37,33 @@ describe('Detail Auto-Layout', () => {
37
37
  expect(inferDetailColumns(15)).toBe(3);
38
38
  expect(inferDetailColumns(50)).toBe(3);
39
39
  });
40
+
41
+ it('should cap to 1 column when containerWidth < 640', () => {
42
+ expect(inferDetailColumns(15, 500)).toBe(1);
43
+ expect(inferDetailColumns(5, 639)).toBe(1);
44
+ });
45
+
46
+ it('should cap to 2 columns when containerWidth < 900', () => {
47
+ expect(inferDetailColumns(15, 800)).toBe(2);
48
+ expect(inferDetailColumns(15, 640)).toBe(2);
49
+ expect(inferDetailColumns(5, 899)).toBe(2);
50
+ });
51
+
52
+ it('should not cap columns when containerWidth >= 900', () => {
53
+ expect(inferDetailColumns(15, 900)).toBe(3);
54
+ expect(inferDetailColumns(15, 1200)).toBe(3);
55
+ });
56
+
57
+ it('should not cap below field-count inference', () => {
58
+ // 2 fields → 1 column, containerWidth 800 would cap at 2, but inference says 1
59
+ expect(inferDetailColumns(2, 800)).toBe(1);
60
+ });
61
+
62
+ it('should still work without containerWidth (backward compatible)', () => {
63
+ expect(inferDetailColumns(15)).toBe(3);
64
+ expect(inferDetailColumns(5)).toBe(2);
65
+ expect(inferDetailColumns(2)).toBe(1);
66
+ });
40
67
  });
41
68
 
42
69
  describe('isWideFieldType', () => {
@@ -180,5 +207,22 @@ describe('Detail Auto-Layout', () => {
180
207
  expect(result.columns).toBe(2);
181
208
  expect(result.fields.length).toBe(8);
182
209
  });
210
+
211
+ it('should cap columns based on containerWidth when provided', () => {
212
+ const fields = Array.from({ length: 15 }, (_, i) => ({
213
+ name: `f${i}`, label: `F${i}`, type: 'text',
214
+ }));
215
+ // Narrow container: should cap to 1
216
+ const result = applyDetailAutoLayout(fields, undefined, 500);
217
+ expect(result.columns).toBe(1);
218
+ });
219
+
220
+ it('should still respect explicit schemaColumns regardless of containerWidth', () => {
221
+ const fields = Array.from({ length: 15 }, (_, i) => ({
222
+ name: `f${i}`, label: `F${i}`, type: 'text',
223
+ }));
224
+ const result = applyDetailAutoLayout(fields, 3, 500);
225
+ expect(result.columns).toBe(3);
226
+ });
183
227
  });
184
228
  });
package/src/autoLayout.ts CHANGED
@@ -46,16 +46,31 @@ export function isWideFieldType(type: string): boolean {
46
46
 
47
47
  /**
48
48
  * Infer optimal number of columns for a detail section based on field count.
49
+ * When containerWidth is provided, limits columns for narrower viewports.
49
50
  *
50
- * Rules:
51
+ * Rules (field-count based):
51
52
  * - 0-3 fields → 1 column
52
53
  * - 4-10 fields → 2 columns
53
54
  * - 11+ fields → 3 columns
55
+ *
56
+ * Responsive capping (when containerWidth is supplied):
57
+ * - containerWidth < 640px → max 1 column
58
+ * - containerWidth < 900px → max 2 columns
59
+ * - containerWidth >= 900px → no cap
54
60
  */
55
- export function inferDetailColumns(fieldCount: number): number {
56
- if (fieldCount <= 3) return 1;
57
- if (fieldCount <= 10) return 2;
58
- return 3;
61
+ export function inferDetailColumns(fieldCount: number, containerWidth?: number): number {
62
+ let cols: number;
63
+ if (fieldCount <= 3) cols = 1;
64
+ else if (fieldCount <= 10) cols = 2;
65
+ else cols = 3;
66
+
67
+ // Apply responsive capping when container width is known
68
+ if (containerWidth !== undefined) {
69
+ if (containerWidth < 640) return Math.min(cols, 1);
70
+ if (containerWidth < 900) return Math.min(cols, 2);
71
+ }
72
+
73
+ return cols;
59
74
  }
60
75
 
61
76
  /**
@@ -89,11 +104,13 @@ export function applyAutoSpan(
89
104
  *
90
105
  * @param fields - The section fields
91
106
  * @param schemaColumns - User-provided columns (from DetailViewSection or DetailViewSchema)
107
+ * @param containerWidth - Optional container width in px for responsive column capping
92
108
  * @returns Object with processed fields and inferred columns
93
109
  */
94
110
  export function applyDetailAutoLayout(
95
111
  fields: DetailViewField[],
96
- schemaColumns: number | undefined
112
+ schemaColumns: number | undefined,
113
+ containerWidth?: number
97
114
  ): { fields: DetailViewField[]; columns: number } {
98
115
  // If user explicitly set columns, respect it but still apply auto span
99
116
  if (schemaColumns !== undefined) {
@@ -101,8 +118,8 @@ export function applyDetailAutoLayout(
101
118
  return { fields: processed, columns: schemaColumns };
102
119
  }
103
120
 
104
- // Infer columns from field count
105
- const columns = inferDetailColumns(fields.length);
121
+ // Infer columns from field count (with optional container-width capping)
122
+ const columns = inferDetailColumns(fields.length, containerWidth);
106
123
 
107
124
  // Apply auto span for wide fields
108
125
  const processed = applyAutoSpan(fields, columns);
package/src/index.tsx CHANGED
@@ -36,7 +36,7 @@ export { SubscriptionToggle } from './SubscriptionToggle';
36
36
  export { ReactionPicker } from './ReactionPicker';
37
37
  export { ThreadedReplies } from './ThreadedReplies';
38
38
  export type { DetailViewProps } from './DetailView';
39
- export type { DetailSectionProps } from './DetailSection';
39
+ export type { DetailSectionProps, VirtualScrollOptions } from './DetailSection';
40
40
  export type { DetailTabsProps } from './DetailTabs';
41
41
  export type { RelatedListProps } from './RelatedList';
42
42
  export type { SectionGroupProps } from './SectionGroup';
@@ -1,96 +0,0 @@
1
- import { jsx as l, jsxs as r } from "react/jsx-runtime";
2
- import { useState as y } from "react";
3
- import { h as u, X as D, D as j, i as k, B, j as C, k as O, l as F, m as I, I as L } from "./index-V_WBvcaA.js";
4
- function R({ value: s, onChange: n, field: x, readonly: g, ...b }) {
5
- const [N, f] = y(!1), [h, v] = y(""), c = x || b.schema, o = c?.options || [], i = c.multiple || !1, d = c.display_field || "label", p = o.filter(
6
- (e) => e.label.toLowerCase().includes(h.toLowerCase())
7
- ), t = i ? (Array.isArray(s) ? s : []).map(
8
- (e) => o.find((a) => a.value === e)
9
- ).filter(Boolean) : s ? [o.find((e) => e.value === s)].filter(Boolean) : [], w = (e) => {
10
- if (i) {
11
- const a = Array.isArray(s) ? s : [], m = a.includes(e.value);
12
- n(m ? a.filter((A) => A !== e.value) : [...a, e.value]);
13
- } else
14
- n(e.value), f(!1);
15
- }, S = (e) => {
16
- if (i) {
17
- const a = Array.isArray(s) ? s : [];
18
- n(a.filter((m) => m !== e));
19
- } else
20
- n(null);
21
- };
22
- return g ? t.length ? i ? /* @__PURE__ */ l("div", { className: "flex flex-wrap gap-1", children: t.map((e, a) => /* @__PURE__ */ l(u, { variant: "outline", children: e?.[d] || e?.label }, a)) }) : /* @__PURE__ */ l("span", { className: "text-sm", children: t[0]?.[d] || t[0]?.label }) : /* @__PURE__ */ l("span", { className: "text-sm", children: "-" }) : /* @__PURE__ */ r("div", { className: "space-y-2", children: [
23
- t.length > 0 && /* @__PURE__ */ l("div", { className: "flex flex-wrap gap-1", children: t.map((e, a) => /* @__PURE__ */ r(
24
- u,
25
- {
26
- variant: "outline",
27
- className: "gap-1",
28
- children: [
29
- e?.[d] || e?.label,
30
- /* @__PURE__ */ l(
31
- "button",
32
- {
33
- onClick: () => S(e?.value),
34
- className: "ml-1 hover:text-destructive",
35
- type: "button",
36
- "aria-label": `Remove ${e?.[d] || e?.label}`,
37
- children: /* @__PURE__ */ l(D, { className: "size-3" })
38
- }
39
- )
40
- ]
41
- },
42
- a
43
- )) }),
44
- /* @__PURE__ */ r(j, { open: N, onOpenChange: f, children: [
45
- /* @__PURE__ */ l(k, { asChild: !0, children: /* @__PURE__ */ r(
46
- B,
47
- {
48
- variant: "outline",
49
- className: "w-full justify-start text-left font-normal",
50
- type: "button",
51
- children: [
52
- /* @__PURE__ */ l(C, { className: "mr-2 size-4" }),
53
- t.length === 0 ? c?.placeholder || "Select..." : i ? `${t.length} selected` : "Change selection"
54
- ]
55
- }
56
- ) }),
57
- /* @__PURE__ */ r(O, { className: "max-w-md", children: [
58
- /* @__PURE__ */ l(F, { children: /* @__PURE__ */ r(I, { children: [
59
- c?.label || "Select",
60
- " ",
61
- i && "(multiple)"
62
- ] }) }),
63
- /* @__PURE__ */ r("div", { className: "space-y-4", children: [
64
- /* @__PURE__ */ l(
65
- L,
66
- {
67
- placeholder: "Search...",
68
- value: h,
69
- onChange: (e) => v(e.target.value),
70
- className: "w-full"
71
- }
72
- ),
73
- /* @__PURE__ */ l("div", { className: "max-h-64 overflow-y-auto space-y-1", children: p.length === 0 ? /* @__PURE__ */ l("p", { className: "text-sm text-gray-500 text-center py-4", children: "No options found" }) : p.map((e) => {
74
- const a = i ? (Array.isArray(s) ? s : []).includes(e.value) : s === e.value;
75
- return /* @__PURE__ */ r(
76
- "button",
77
- {
78
- onClick: () => w(e),
79
- className: `w-full text-left px-3 py-2 rounded-md text-sm hover:bg-gray-100 flex items-center justify-between ${a ? "bg-blue-50 text-blue-700" : ""}`,
80
- type: "button",
81
- children: [
82
- /* @__PURE__ */ l("span", { children: e.label }),
83
- a && /* @__PURE__ */ l(u, { variant: "default", className: "ml-2", children: "Selected" })
84
- ]
85
- },
86
- e.value
87
- );
88
- }) })
89
- ] })
90
- ] })
91
- ] })
92
- ] });
93
- }
94
- export {
95
- R as LookupField
96
- };