@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.
- package/.turbo/turbo-build.log +43 -41
- package/CHANGELOG.md +10 -0
- package/dist/{AddressField-QBIlXCFl.js → AddressField-BtiTrEpf.js} +1 -1
- package/dist/{AvatarField-BEZuQTAH.js → AvatarField-CwlnWNSf.js} +7 -7
- package/dist/{BooleanField-doa93aFX.js → BooleanField-DpMXU2ya.js} +1 -1
- package/dist/{CodeField-jVV-hIXg.js → CodeField-gwmcFihg.js} +2 -2
- package/dist/{ColorField-B53qKQGW.js → ColorField-CWmF_zoW.js} +1 -1
- package/dist/{CurrencyField-og0NJ2ax.js → CurrencyField-BF3tYAgm.js} +1 -1
- package/dist/{DateField-BFx64AtG.js → DateField-a6Ka9ph2.js} +1 -1
- package/dist/{DateTimeField-Cxs2Rx2f.js → DateTimeField-C4wWOEiw.js} +1 -1
- package/dist/{EmailField-BfcpzRe7.js → EmailField-DJqiQ4sp.js} +1 -1
- package/dist/{FileField-KarqvhYm.js → FileField-ChjjCydz.js} +1 -1
- package/dist/{GeolocationField-B5SKZaqn.js → GeolocationField-BnkeUBek.js} +1 -1
- package/dist/{GridField-DOotrUTo.js → GridField-DoHqc2ON.js} +6 -6
- package/dist/{ImageField-Ddotp4u-.js → ImageField-Ld7SHA8N.js} +1 -1
- package/dist/{LocationField-tOkQaPIM.js → LocationField-Bgu-vMAE.js} +1 -1
- package/dist/{MasterDetailField-CpHw3nTE.js → MasterDetailField-Bp5WBTzU.js} +3 -3
- package/dist/{NumberField-CzBb2a28.js → NumberField-uBqVZ-gt.js} +1 -1
- package/dist/{ObjectField-BoL-JqE4.js → ObjectField-BH1Md9gH.js} +6 -6
- package/dist/{PasswordField-DrTzkYgj.js → PasswordField-D8GZjY7d.js} +1 -1
- package/dist/{PercentField-B9ZUQ3zE.js → PercentField-DyK8vg8M.js} +1 -1
- package/dist/{PhoneField-Bf9lhpdu.js → PhoneField-B3qJyLP0.js} +1 -1
- package/dist/{QRCodeField-PzMpdBKd.js → QRCodeField-CGiRTCZq.js} +1 -1
- package/dist/{RatingField-CeBMFe8o.js → RatingField-CWVaJNyf.js} +4 -4
- package/dist/{RichTextField-Ch7CHSQ0.js → RichTextField-CusveP9T.js} +1 -1
- package/dist/{SelectField-f5Nbi02x.js → SelectField-UdDfsEZo.js} +1 -1
- package/dist/{SignatureField-CpxTX2tR.js → SignatureField-DFvPKbuI.js} +1 -1
- package/dist/{SliderField-BoZtzgcr.js → SliderField-C-HvGV9e.js} +1 -1
- package/dist/{TextAreaField-rT1DLnV2.js → TextAreaField-C5KygUT3.js} +1 -1
- package/dist/{TextField-CflRxusu.js → TextField-oUjuqQ1x.js} +1 -1
- package/dist/{TimeField-DeVeCpRu.js → TimeField-SsQ6rfk5.js} +1 -1
- package/dist/{UrlField-UWKfhP9T.js → UrlField-kd48Ip95.js} +1 -1
- package/dist/{UserField-Cp2zQDjz.js → UserField-BOjE_CAz.js} +11 -11
- package/dist/{index-V_WBvcaA.js → index-D2t9pLAg.js} +56341 -56642
- package/dist/index.js +10 -10
- package/dist/index.umd.cjs +61 -63
- package/dist/plugin-detail.css +1 -1
- package/dist/src/DetailSection.d.ts +10 -0
- package/dist/src/DetailSection.d.ts.map +1 -1
- package/dist/src/HeaderHighlight.d.ts +2 -0
- package/dist/src/HeaderHighlight.d.ts.map +1 -1
- package/dist/src/RecordChatterPanel.d.ts +2 -0
- package/dist/src/RecordChatterPanel.d.ts.map +1 -1
- package/dist/src/autoLayout.d.ts +10 -3
- package/dist/src/autoLayout.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/DetailSection.tsx +40 -1
- package/src/DetailView.tsx +1 -1
- package/src/HeaderHighlight.tsx +22 -1
- package/src/RecordChatterPanel.tsx +6 -1
- package/src/RelatedList.tsx +1 -1
- package/src/__tests__/DetailSection.test.tsx +61 -0
- package/src/__tests__/HeaderHighlight.test.tsx +145 -0
- package/src/__tests__/RecordChatterPanel.test.tsx +38 -0
- package/src/__tests__/RelatedList.test.tsx +3 -3
- package/src/__tests__/autoLayout.test.ts +44 -0
- package/src/autoLayout.ts +25 -8
- package/src/index.tsx +1 -1
- 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
|
-
|
|
57
|
-
if (fieldCount <=
|
|
58
|
-
|
|
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
|
-
};
|