@object-ui/plugin-detail 3.1.5 → 3.3.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.
Files changed (209) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +21 -1
  3. package/dist/AddressField-LgHnO2Lk.js +98 -0
  4. package/dist/AutoNumberField-xZCrU0eW.js +14 -0
  5. package/dist/{AvatarField-YGj51ozd.js → AvatarField-Dy2XGlPz.js} +16 -15
  6. package/dist/{BooleanField-CaA898Tk.js → BooleanField-C0Clfka5.js} +11 -10
  7. package/dist/CodeField-CHUa07B6.js +23 -0
  8. package/dist/ColorField-vxHqEhcS.js +38 -0
  9. package/dist/CurrencyField-DiWjYWDo.js +49 -0
  10. package/dist/DateField-DGaRPM4P.js +22 -0
  11. package/dist/DateTimeField-8QnpsI_h.js +30 -0
  12. package/dist/EmailField-CkVgMbpI.js +26 -0
  13. package/dist/FileField-5UPV7uek.js +149 -0
  14. package/dist/FormulaField-BUgt6-Pi.js +17 -0
  15. package/dist/GeolocationField-D9T_jgG6.js +118 -0
  16. package/dist/GridField-DE_HwiIN.js +49 -0
  17. package/dist/ImageField-Dswnqtzf.js +73 -0
  18. package/dist/LocationField-gjqbE6na.js +36 -0
  19. package/dist/LookupField-BcS3LRKc.js +901 -0
  20. package/dist/{MasterDetailField-I1A9oEGC.js → MasterDetailField-BF6_-X3A.js} +20 -19
  21. package/dist/NumberField-Dj2rYmrS.js +27 -0
  22. package/dist/ObjectField-BymIojwd.js +50 -0
  23. package/dist/{PasswordField-DBtluGJ1.js → PasswordField-ED_Xgqz-.js} +8 -7
  24. package/dist/PercentField-D-JKOxKC.js +61 -0
  25. package/dist/PhoneField-DSCaGYq7.js +26 -0
  26. package/dist/QRCodeField-CtcOUapi.js +73 -0
  27. package/dist/{RatingField-B_Mnr63i.js → RatingField-BDnyQFWy.js} +10 -9
  28. package/dist/RichTextField-CH6LVZQA.js +33 -0
  29. package/dist/SelectField-DE4dpkMV.js +36 -0
  30. package/dist/{SignatureField-CddhEK9u.js → SignatureField-B1wh3f5A.js} +18 -17
  31. package/dist/{SliderField-Df5hMzNc.js → SliderField-zoTCKh9n.js} +2 -1
  32. package/dist/SummaryField-BeBVT6VN.js +22 -0
  33. package/dist/TextAreaField-rfUGrRxh.js +37 -0
  34. package/dist/TextField-C_yM7ATQ.js +30 -0
  35. package/dist/TimeField-BcQmBZi9.js +22 -0
  36. package/dist/UrlField-BakaF6NI.js +31 -0
  37. package/dist/UserField-zS7y3eKb.js +76 -0
  38. package/dist/VectorField-CTZ4myDM.js +34 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +1741 -1504
  41. package/dist/index.umd.cjs +43 -51
  42. package/dist/packages/plugin-detail/src/ActivityTimeline.d.ts.map +1 -0
  43. package/dist/packages/plugin-detail/src/CommentAttachment.d.ts.map +1 -0
  44. package/dist/packages/plugin-detail/src/CommentInput.d.ts.map +1 -0
  45. package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -0
  46. package/dist/packages/plugin-detail/src/DetailTabs.d.ts.map +1 -0
  47. package/dist/packages/plugin-detail/src/DetailView.d.ts +47 -0
  48. package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -0
  49. package/dist/packages/plugin-detail/src/DetailView.stories.d.ts.map +1 -0
  50. package/dist/packages/plugin-detail/src/DiffView.d.ts.map +1 -0
  51. package/dist/packages/plugin-detail/src/FieldChangeItem.d.ts.map +1 -0
  52. package/dist/packages/plugin-detail/src/HeaderHighlight.d.ts.map +1 -0
  53. package/dist/packages/plugin-detail/src/InlineCreateRelated.d.ts.map +1 -0
  54. package/dist/packages/plugin-detail/src/MentionAutocomplete.d.ts.map +1 -0
  55. package/dist/packages/plugin-detail/src/PointInTimeRestore.d.ts.map +1 -0
  56. package/dist/packages/plugin-detail/src/ReactionPicker.d.ts.map +1 -0
  57. package/dist/packages/plugin-detail/src/RecordActivityTimeline.d.ts.map +1 -0
  58. package/dist/packages/plugin-detail/src/RecordChatterPanel.d.ts.map +1 -0
  59. package/dist/packages/plugin-detail/src/RecordComments.d.ts.map +1 -0
  60. package/dist/packages/plugin-detail/src/RecordNavigationEnhanced.d.ts.map +1 -0
  61. package/dist/{src → packages/plugin-detail/src}/RelatedList.d.ts +8 -0
  62. package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -0
  63. package/dist/packages/plugin-detail/src/RelationshipGraph.d.ts.map +1 -0
  64. package/dist/packages/plugin-detail/src/RichTextCommentInput.d.ts.map +1 -0
  65. package/dist/packages/plugin-detail/src/SectionGroup.d.ts.map +1 -0
  66. package/dist/packages/plugin-detail/src/SubscriptionToggle.d.ts.map +1 -0
  67. package/dist/packages/plugin-detail/src/ThreadedReplies.d.ts.map +1 -0
  68. package/dist/packages/plugin-detail/src/autoLayout.d.ts.map +1 -0
  69. package/dist/packages/plugin-detail/src/index.d.ts.map +1 -0
  70. package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -0
  71. package/dist/plugin-detail.css +1 -2
  72. package/dist/rolldown-runtime-DnwLefa7.js +23 -0
  73. package/dist/{src-CXr1-vVl.js → src-DyUKLvMN.js} +29788 -37711
  74. package/dist/useFieldTranslation-BRgjC1oq.js +9 -0
  75. package/package.json +34 -12
  76. package/.turbo/turbo-build.log +0 -61
  77. package/dist/AddressField-DBkEyMcG.js +0 -93
  78. package/dist/AutoNumberField-Baa191z-.js +0 -14
  79. package/dist/CodeField-BU51nl1L.js +0 -22
  80. package/dist/ColorField-Cnf6ZM7c.js +0 -37
  81. package/dist/CurrencyField-Wg-XOId2.js +0 -51
  82. package/dist/DateField-Cth1ky_m.js +0 -21
  83. package/dist/DateTimeField-B0m6FhHL.js +0 -32
  84. package/dist/EmailField-Do7qT_L_.js +0 -28
  85. package/dist/FileField-aRJAdbQb.js +0 -151
  86. package/dist/FormulaField-DTMkagFx.js +0 -14
  87. package/dist/GeolocationField-RqpHWTEv.js +0 -113
  88. package/dist/GridField-D4IH0cpo.js +0 -51
  89. package/dist/ImageField-BYCFajjr.js +0 -75
  90. package/dist/LocationField-Bi_ew9sd.js +0 -35
  91. package/dist/LookupField-BjwlDPtt.js +0 -902
  92. package/dist/NumberField-D_NucQlp.js +0 -26
  93. package/dist/ObjectField-CG-LaM65.js +0 -52
  94. package/dist/PercentField-B6sO_J3i.js +0 -63
  95. package/dist/PhoneField-CcQAWwR6.js +0 -28
  96. package/dist/QRCodeField-CEjWs-J5.js +0 -72
  97. package/dist/RichTextField-qOEJl5Ai.js +0 -32
  98. package/dist/SelectField-C8hWu3gm.js +0 -30
  99. package/dist/SummaryField-DgiFm-Cr.js +0 -19
  100. package/dist/TextAreaField-DuriTqsD.js +0 -36
  101. package/dist/TextField-CGNSl7RU.js +0 -29
  102. package/dist/TimeField-YO58ctFg.js +0 -21
  103. package/dist/UrlField-1-BMM1jn.js +0 -33
  104. package/dist/UserField-B6GqxP_S.js +0 -78
  105. package/dist/VectorField-BkEjbSt0.js +0 -36
  106. package/dist/src/ActivityTimeline.d.ts.map +0 -1
  107. package/dist/src/CommentAttachment.d.ts.map +0 -1
  108. package/dist/src/CommentInput.d.ts.map +0 -1
  109. package/dist/src/DetailSection.d.ts.map +0 -1
  110. package/dist/src/DetailTabs.d.ts.map +0 -1
  111. package/dist/src/DetailView.d.ts +0 -23
  112. package/dist/src/DetailView.d.ts.map +0 -1
  113. package/dist/src/DetailView.stories.d.ts.map +0 -1
  114. package/dist/src/DiffView.d.ts.map +0 -1
  115. package/dist/src/FieldChangeItem.d.ts.map +0 -1
  116. package/dist/src/HeaderHighlight.d.ts.map +0 -1
  117. package/dist/src/InlineCreateRelated.d.ts.map +0 -1
  118. package/dist/src/MentionAutocomplete.d.ts.map +0 -1
  119. package/dist/src/PointInTimeRestore.d.ts.map +0 -1
  120. package/dist/src/ReactionPicker.d.ts.map +0 -1
  121. package/dist/src/RecordActivityTimeline.d.ts.map +0 -1
  122. package/dist/src/RecordChatterPanel.d.ts.map +0 -1
  123. package/dist/src/RecordComments.d.ts.map +0 -1
  124. package/dist/src/RecordNavigationEnhanced.d.ts.map +0 -1
  125. package/dist/src/RelatedList.d.ts.map +0 -1
  126. package/dist/src/RelationshipGraph.d.ts.map +0 -1
  127. package/dist/src/RichTextCommentInput.d.ts.map +0 -1
  128. package/dist/src/SectionGroup.d.ts.map +0 -1
  129. package/dist/src/SubscriptionToggle.d.ts.map +0 -1
  130. package/dist/src/ThreadedReplies.d.ts.map +0 -1
  131. package/dist/src/autoLayout.d.ts.map +0 -1
  132. package/dist/src/index.d.ts.map +0 -1
  133. package/dist/src/useDetailTranslation.d.ts.map +0 -1
  134. package/src/ActivityTimeline.tsx +0 -184
  135. package/src/CommentAttachment.tsx +0 -192
  136. package/src/CommentInput.tsx +0 -81
  137. package/src/DetailSection.tsx +0 -340
  138. package/src/DetailTabs.tsx +0 -73
  139. package/src/DetailView.stories.tsx +0 -334
  140. package/src/DetailView.tsx +0 -823
  141. package/src/DiffView.tsx +0 -231
  142. package/src/FieldChangeItem.tsx +0 -46
  143. package/src/HeaderHighlight.tsx +0 -88
  144. package/src/InlineCreateRelated.tsx +0 -291
  145. package/src/MentionAutocomplete.tsx +0 -123
  146. package/src/PointInTimeRestore.tsx +0 -261
  147. package/src/ReactionPicker.tsx +0 -106
  148. package/src/RecordActivityTimeline.tsx +0 -429
  149. package/src/RecordChatterPanel.tsx +0 -207
  150. package/src/RecordComments.tsx +0 -215
  151. package/src/RecordNavigationEnhanced.tsx +0 -211
  152. package/src/RelatedList.tsx +0 -413
  153. package/src/RelationshipGraph.tsx +0 -286
  154. package/src/RichTextCommentInput.tsx +0 -348
  155. package/src/SectionGroup.tsx +0 -101
  156. package/src/SubscriptionToggle.tsx +0 -60
  157. package/src/ThreadedReplies.tsx +0 -161
  158. package/src/__tests__/ActivityTimeline.test.tsx +0 -119
  159. package/src/__tests__/ActivityTimelineFiltering.test.tsx +0 -143
  160. package/src/__tests__/CommentInput.test.tsx +0 -57
  161. package/src/__tests__/DetailSection.test.tsx +0 -490
  162. package/src/__tests__/DetailView.test.tsx +0 -694
  163. package/src/__tests__/FieldChangeItem.test.tsx +0 -119
  164. package/src/__tests__/HeaderHighlight.test.tsx +0 -213
  165. package/src/__tests__/MentionAutocomplete.test.tsx +0 -97
  166. package/src/__tests__/ReactionPicker.test.tsx +0 -113
  167. package/src/__tests__/RecordActivityTimeline.test.tsx +0 -395
  168. package/src/__tests__/RecordChatterPanel.test.tsx +0 -265
  169. package/src/__tests__/RecordComments.test.tsx +0 -96
  170. package/src/__tests__/RecordCommentsPinSearch.test.tsx +0 -133
  171. package/src/__tests__/RelatedList.test.tsx +0 -160
  172. package/src/__tests__/SectionGroup.test.tsx +0 -101
  173. package/src/__tests__/SubscriptionToggle.test.tsx +0 -84
  174. package/src/__tests__/ThreadedReplies.test.tsx +0 -212
  175. package/src/__tests__/autoLayout.test.ts +0 -228
  176. package/src/__tests__/phase12-features.test.tsx +0 -583
  177. package/src/__tests__/roadmap-features.test.tsx +0 -478
  178. package/src/autoLayout.ts +0 -128
  179. package/src/index.tsx +0 -149
  180. package/src/useDetailTranslation.ts +0 -114
  181. package/tsconfig.json +0 -18
  182. package/vite.config.ts +0 -56
  183. package/vitest.config.ts +0 -13
  184. package/vitest.setup.ts +0 -1
  185. /package/dist/{src → packages/plugin-detail/src}/ActivityTimeline.d.ts +0 -0
  186. /package/dist/{src → packages/plugin-detail/src}/CommentAttachment.d.ts +0 -0
  187. /package/dist/{src → packages/plugin-detail/src}/CommentInput.d.ts +0 -0
  188. /package/dist/{src → packages/plugin-detail/src}/DetailSection.d.ts +0 -0
  189. /package/dist/{src → packages/plugin-detail/src}/DetailTabs.d.ts +0 -0
  190. /package/dist/{src → packages/plugin-detail/src}/DetailView.stories.d.ts +0 -0
  191. /package/dist/{src → packages/plugin-detail/src}/DiffView.d.ts +0 -0
  192. /package/dist/{src → packages/plugin-detail/src}/FieldChangeItem.d.ts +0 -0
  193. /package/dist/{src → packages/plugin-detail/src}/HeaderHighlight.d.ts +0 -0
  194. /package/dist/{src → packages/plugin-detail/src}/InlineCreateRelated.d.ts +0 -0
  195. /package/dist/{src → packages/plugin-detail/src}/MentionAutocomplete.d.ts +0 -0
  196. /package/dist/{src → packages/plugin-detail/src}/PointInTimeRestore.d.ts +0 -0
  197. /package/dist/{src → packages/plugin-detail/src}/ReactionPicker.d.ts +0 -0
  198. /package/dist/{src → packages/plugin-detail/src}/RecordActivityTimeline.d.ts +0 -0
  199. /package/dist/{src → packages/plugin-detail/src}/RecordChatterPanel.d.ts +0 -0
  200. /package/dist/{src → packages/plugin-detail/src}/RecordComments.d.ts +0 -0
  201. /package/dist/{src → packages/plugin-detail/src}/RecordNavigationEnhanced.d.ts +0 -0
  202. /package/dist/{src → packages/plugin-detail/src}/RelationshipGraph.d.ts +0 -0
  203. /package/dist/{src → packages/plugin-detail/src}/RichTextCommentInput.d.ts +0 -0
  204. /package/dist/{src → packages/plugin-detail/src}/SectionGroup.d.ts +0 -0
  205. /package/dist/{src → packages/plugin-detail/src}/SubscriptionToggle.d.ts +0 -0
  206. /package/dist/{src → packages/plugin-detail/src}/ThreadedReplies.d.ts +0 -0
  207. /package/dist/{src → packages/plugin-detail/src}/autoLayout.d.ts +0 -0
  208. /package/dist/{src → packages/plugin-detail/src}/index.d.ts +0 -0
  209. /package/dist/{src → packages/plugin-detail/src}/useDetailTranslation.d.ts +0 -0
@@ -1,478 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { describe, it, expect, vi } from 'vitest';
10
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
11
- import { DetailView } from '../DetailView';
12
- import { RelatedList } from '../RelatedList';
13
- import type { DetailViewSchema } from '@object-ui/types';
14
-
15
- describe('Roadmap Features', () => {
16
- // ── Feature 1: Auto-discover related lists ──
17
- describe('Auto-discover related lists', () => {
18
- it('should auto-discover related lists from objectSchema reference fields', async () => {
19
- const mockDataSource = {
20
- getObjectSchema: vi.fn().mockResolvedValue({
21
- fields: {
22
- name: { type: 'text' },
23
- account: { type: 'lookup', reference_to: 'account', label: 'Account' },
24
- contact: { type: 'master_detail', reference_to: 'contact', label: 'Primary Contact' },
25
- },
26
- }),
27
- findOne: vi.fn().mockResolvedValue({ name: 'Order 1' }),
28
- } as any;
29
-
30
- const schema: DetailViewSchema = {
31
- type: 'detail-view',
32
- title: 'Order Details',
33
- objectName: 'order',
34
- resourceId: 'order-1',
35
- fields: [{ name: 'name', label: 'Name' }],
36
- autoDiscoverRelated: true,
37
- };
38
-
39
- render(<DetailView schema={schema} dataSource={mockDataSource} />);
40
-
41
- // Wait for data to load
42
- await waitFor(() => {
43
- expect(screen.getByText('Order 1')).toBeInTheDocument();
44
- });
45
-
46
- // Should show auto-discovered related lists
47
- expect(screen.getByText('Account')).toBeInTheDocument();
48
- expect(screen.getByText('Primary Contact')).toBeInTheDocument();
49
- });
50
-
51
- it('should not auto-discover when autoDiscoverRelated is false', () => {
52
- const schema: DetailViewSchema = {
53
- type: 'detail-view',
54
- title: 'Order Details',
55
- data: { name: 'Order 1' },
56
- fields: [{ name: 'name', label: 'Name' }],
57
- autoDiscoverRelated: false,
58
- };
59
-
60
- render(<DetailView schema={schema} />);
61
- // Should not show "Related" heading
62
- expect(screen.queryByText('Related')).not.toBeInTheDocument();
63
- });
64
-
65
- it('should not auto-discover when explicit related lists are provided', async () => {
66
- const mockDataSource = {
67
- getObjectSchema: vi.fn().mockResolvedValue({
68
- fields: {
69
- name: { type: 'text' },
70
- account: { type: 'lookup', reference_to: 'account', label: 'Account' },
71
- },
72
- }),
73
- findOne: vi.fn().mockResolvedValue({ name: 'Order 1' }),
74
- } as any;
75
-
76
- const schema: DetailViewSchema = {
77
- type: 'detail-view',
78
- title: 'Order Details',
79
- objectName: 'order',
80
- resourceId: 'order-1',
81
- fields: [{ name: 'name', label: 'Name' }],
82
- autoDiscoverRelated: true,
83
- related: [
84
- { title: 'Custom Related', type: 'table', data: [] },
85
- ],
86
- };
87
-
88
- render(<DetailView schema={schema} dataSource={mockDataSource} />);
89
-
90
- await waitFor(() => {
91
- expect(screen.getByText('Order 1')).toBeInTheDocument();
92
- });
93
-
94
- // Should show explicit related, not auto-discovered
95
- expect(screen.getByText('Custom Related')).toBeInTheDocument();
96
- });
97
- });
98
-
99
- // ── Feature 2: Auto Tabs layout ──
100
- describe('Auto Tabs layout', () => {
101
- it('should render Details/Related/Activity tabs when autoTabs is true', () => {
102
- const schema: DetailViewSchema = {
103
- type: 'detail-view',
104
- title: 'Account Details',
105
- data: { name: 'Acme Corp' },
106
- fields: [{ name: 'name', label: 'Name' }],
107
- autoTabs: true,
108
- related: [
109
- { title: 'Contacts', type: 'table', data: [] },
110
- ],
111
- activities: [
112
- { id: '1', type: 'create', user: 'Bob', timestamp: '2026-02-15T10:00:00Z' },
113
- ],
114
- };
115
-
116
- render(<DetailView schema={schema} />);
117
-
118
- // All three tabs should be present
119
- expect(screen.getByText('Details')).toBeInTheDocument();
120
- expect(screen.getByText('Related')).toBeInTheDocument();
121
- });
122
-
123
- it('should show sections inside Details tab when autoTabs is true', () => {
124
- const schema: DetailViewSchema = {
125
- type: 'detail-view',
126
- title: 'Account Details',
127
- data: { name: 'Acme Corp', email: 'acme@example.com' },
128
- sections: [
129
- {
130
- title: 'Basic Info',
131
- fields: [
132
- { name: 'name', label: 'Name' },
133
- { name: 'email', label: 'Email' },
134
- ],
135
- },
136
- ],
137
- autoTabs: true,
138
- };
139
-
140
- render(<DetailView schema={schema} />);
141
-
142
- // Details tab should be active by default
143
- expect(screen.getByText('Basic Info')).toBeInTheDocument();
144
- expect(screen.getByText('Acme Corp')).toBeInTheDocument();
145
- });
146
-
147
- it('should not render autoTabs when explicit tabs are provided', () => {
148
- const schema: DetailViewSchema = {
149
- type: 'detail-view',
150
- title: 'Account',
151
- data: { name: 'Acme' },
152
- fields: [{ name: 'name', label: 'Name' }],
153
- autoTabs: true,
154
- tabs: [
155
- { key: 'custom', label: 'Custom Tab', content: { type: 'text', text: 'Custom' } },
156
- ],
157
- };
158
-
159
- render(<DetailView schema={schema} />);
160
- // Should not render auto-tabs Details/Related/Activity
161
- // Instead renders explicit tabs
162
- expect(screen.queryByRole('tab', { name: 'Details' })).not.toBeInTheDocument();
163
- });
164
- });
165
-
166
- // ── Feature 3: Related list row-level Edit/Delete ──
167
- describe('Related list row-level actions', () => {
168
- it('should render Edit button for each row when onRowEdit is provided', () => {
169
- const onRowEdit = vi.fn();
170
- const data = [
171
- { id: 1, name: 'Alice' },
172
- { id: 2, name: 'Bob' },
173
- ];
174
-
175
- render(
176
- <RelatedList
177
- title="Contacts"
178
- type="table"
179
- data={data}
180
- onRowEdit={onRowEdit}
181
- />
182
- );
183
-
184
- const editButtons = screen.getAllByText('Edit');
185
- expect(editButtons.length).toBe(2);
186
- });
187
-
188
- it('should call onRowEdit with the correct row when clicked', () => {
189
- const onRowEdit = vi.fn();
190
- const data = [{ id: 1, name: 'Alice' }];
191
-
192
- render(
193
- <RelatedList
194
- title="Contacts"
195
- type="table"
196
- data={data}
197
- onRowEdit={onRowEdit}
198
- />
199
- );
200
-
201
- fireEvent.click(screen.getByText('Edit'));
202
- expect(onRowEdit).toHaveBeenCalledWith({ id: 1, name: 'Alice' });
203
- });
204
-
205
- it('should render Delete button for each row when onRowDelete is provided', () => {
206
- const onRowDelete = vi.fn();
207
- const data = [
208
- { id: 1, name: 'Alice' },
209
- { id: 2, name: 'Bob' },
210
- ];
211
-
212
- render(
213
- <RelatedList
214
- title="Contacts"
215
- type="table"
216
- data={data}
217
- onRowDelete={onRowDelete}
218
- />
219
- );
220
-
221
- const deleteButtons = screen.getAllByText('Delete');
222
- expect(deleteButtons.length).toBe(2);
223
- });
224
-
225
- it('should call onRowDelete with the correct row after confirmation', () => {
226
- const onRowDelete = vi.fn();
227
- const data = [{ id: 1, name: 'Alice' }];
228
- const confirmSpy = vi.fn().mockReturnValue(true);
229
- window.confirm = confirmSpy;
230
-
231
- render(
232
- <RelatedList
233
- title="Contacts"
234
- type="table"
235
- data={data}
236
- onRowDelete={onRowDelete}
237
- />
238
- );
239
-
240
- fireEvent.click(screen.getByText('Delete'));
241
- expect(confirmSpy).toHaveBeenCalled();
242
- expect(onRowDelete).toHaveBeenCalledWith({ id: 1, name: 'Alice' });
243
- });
244
-
245
- it('should not call onRowDelete when confirmation is cancelled', () => {
246
- const onRowDelete = vi.fn();
247
- const data = [{ id: 1, name: 'Alice' }];
248
- const confirmSpy = vi.fn().mockReturnValue(false);
249
- window.confirm = confirmSpy;
250
-
251
- render(
252
- <RelatedList
253
- title="Contacts"
254
- type="table"
255
- data={data}
256
- onRowDelete={onRowDelete}
257
- />
258
- );
259
-
260
- fireEvent.click(screen.getByText('Delete'));
261
- expect(onRowDelete).not.toHaveBeenCalled();
262
- });
263
- });
264
-
265
- // ── Feature 4: Related list pagination, sorting, filtering ──
266
- describe('Related list pagination', () => {
267
- const manyItems = Array.from({ length: 15 }, (_, i) => ({
268
- id: i + 1,
269
- name: `Item ${i + 1}`,
270
- }));
271
-
272
- it('should show pagination controls when pageSize is set', () => {
273
- render(
274
- <RelatedList
275
- title="Items"
276
- type="table"
277
- data={manyItems}
278
- pageSize={5}
279
- />
280
- );
281
-
282
- expect(screen.getByText('Page 1 of 3')).toBeInTheDocument();
283
- expect(screen.getByText('Next')).toBeInTheDocument();
284
- expect(screen.getByText('Previous')).toBeInTheDocument();
285
- });
286
-
287
- it('should navigate to next page', () => {
288
- render(
289
- <RelatedList
290
- title="Items"
291
- type="table"
292
- data={manyItems}
293
- pageSize={5}
294
- />
295
- );
296
-
297
- fireEvent.click(screen.getByText('Next'));
298
- expect(screen.getByText('Page 2 of 3')).toBeInTheDocument();
299
- });
300
-
301
- it('should disable Previous on first page', () => {
302
- render(
303
- <RelatedList
304
- title="Items"
305
- type="table"
306
- data={manyItems}
307
- pageSize={5}
308
- />
309
- );
310
-
311
- const prevButton = screen.getByText('Previous').closest('button');
312
- expect(prevButton).toBeDisabled();
313
- });
314
-
315
- it('should not show pagination when all items fit on one page', () => {
316
- const fewItems = [{ id: 1, name: 'Item 1' }];
317
- render(
318
- <RelatedList
319
- title="Items"
320
- type="table"
321
- data={fewItems}
322
- pageSize={5}
323
- />
324
- );
325
-
326
- expect(screen.queryByText(/Page \d+ of \d+/)).not.toBeInTheDocument();
327
- });
328
- });
329
-
330
- describe('Related list filtering', () => {
331
- const data = [
332
- { id: 1, name: 'Alice' },
333
- { id: 2, name: 'Bob' },
334
- { id: 3, name: 'Charlie' },
335
- ];
336
-
337
- it('should render filter input when filterable is true', () => {
338
- render(
339
- <RelatedList
340
- title="Contacts"
341
- type="table"
342
- data={data}
343
- filterable={true}
344
- />
345
- );
346
-
347
- expect(screen.getByPlaceholderText('Filter...')).toBeInTheDocument();
348
- });
349
-
350
- it('should not render filter input when filterable is false', () => {
351
- render(
352
- <RelatedList
353
- title="Contacts"
354
- type="table"
355
- data={data}
356
- filterable={false}
357
- />
358
- );
359
-
360
- expect(screen.queryByPlaceholderText('Filter...')).not.toBeInTheDocument();
361
- });
362
- });
363
-
364
- describe('Related list sorting', () => {
365
- const data = [
366
- { id: 1, name: 'Charlie' },
367
- { id: 2, name: 'Alice' },
368
- { id: 3, name: 'Bob' },
369
- ];
370
-
371
- const columns = [
372
- { accessorKey: 'name', header: 'Name' },
373
- ];
374
-
375
- it('should render sort buttons when sortable is true', () => {
376
- render(
377
- <RelatedList
378
- title="Contacts"
379
- type="table"
380
- data={data}
381
- columns={columns}
382
- sortable={true}
383
- />
384
- );
385
-
386
- // Sort buttons include an ArrowUpDown icon
387
- const sortBtns = screen.getAllByRole('button').filter(btn =>
388
- btn.querySelector('.lucide-arrow-up-down')
389
- );
390
- expect(sortBtns.length).toBe(1);
391
- });
392
-
393
- it('should not render sort buttons when sortable is false', () => {
394
- render(
395
- <RelatedList
396
- title="Contacts"
397
- type="table"
398
- data={data}
399
- columns={columns}
400
- sortable={false}
401
- />
402
- );
403
-
404
- // Name appears as a sort button only when sortable; verify no ArrowUpDown icon
405
- const sortBtns = screen.queryAllByRole('button').filter(btn =>
406
- btn.querySelector('.lucide-arrow-up-down')
407
- );
408
- expect(sortBtns.length).toBe(0);
409
- });
410
- });
411
-
412
- // ── Feature 5: Collapsible section groups ──
413
- describe('Collapsible section groups in DetailView', () => {
414
- it('should render section groups', () => {
415
- const schema: DetailViewSchema = {
416
- type: 'detail-view',
417
- title: 'Account Details',
418
- data: { billingStreet: '123 Main St', shippingStreet: '456 Oak Ave' },
419
- fields: [],
420
- sectionGroups: [
421
- {
422
- title: 'Address Information',
423
- sections: [
424
- {
425
- title: 'Billing',
426
- fields: [{ name: 'billingStreet', label: 'Street' }],
427
- },
428
- {
429
- title: 'Shipping',
430
- fields: [{ name: 'shippingStreet', label: 'Street' }],
431
- },
432
- ],
433
- },
434
- ],
435
- };
436
-
437
- render(<DetailView schema={schema} />);
438
- expect(screen.getByText('Address Information')).toBeInTheDocument();
439
- expect(screen.getByText('Billing')).toBeInTheDocument();
440
- expect(screen.getByText('Shipping')).toBeInTheDocument();
441
- });
442
- });
443
-
444
- // ── Feature 6: Header highlight area ──
445
- describe('Header highlight area', () => {
446
- it('should render highlight fields below the header', () => {
447
- const schema: DetailViewSchema = {
448
- type: 'detail-view',
449
- title: 'Account Details',
450
- data: { name: 'Acme Corp', revenue: '$5M', employees: 150 },
451
- fields: [{ name: 'name', label: 'Name' }],
452
- highlightFields: [
453
- { name: 'revenue', label: 'Annual Revenue' },
454
- { name: 'employees', label: 'Employees' },
455
- ],
456
- };
457
-
458
- render(<DetailView schema={schema} />);
459
- expect(screen.getByText('Annual Revenue')).toBeInTheDocument();
460
- expect(screen.getByText('$5M')).toBeInTheDocument();
461
- expect(screen.getByText('Employees')).toBeInTheDocument();
462
- expect(screen.getByText('150')).toBeInTheDocument();
463
- });
464
-
465
- it('should not render highlight area when no highlightFields are provided', () => {
466
- const schema: DetailViewSchema = {
467
- type: 'detail-view',
468
- title: 'Account Details',
469
- data: { name: 'Acme Corp' },
470
- fields: [{ name: 'name', label: 'Name' }],
471
- };
472
-
473
- const { container } = render(<DetailView schema={schema} />);
474
- // No highlight card should be present
475
- expect(container.querySelector('.border-dashed')).not.toBeInTheDocument();
476
- });
477
- });
478
- });
package/src/autoLayout.ts DELETED
@@ -1,128 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- /**
10
- * Auto-Layout for DetailView
11
- *
12
- * Provides intelligent, zero-configuration default layout for detail sections.
13
- * When the user has not explicitly set columns on a section, this module
14
- * infers optimal column count based on the number of fields.
15
- *
16
- * Priority: User configuration > Auto-layout inference
17
- *
18
- * Column rules for detail views (wider thresholds than forms):
19
- * - 0-3 fields → 1 column
20
- * - 4-10 fields → 2 columns
21
- * - 11+ fields → 3 columns
22
- */
23
-
24
- import type { DetailViewField } from '@object-ui/types';
25
-
26
- /** Field types that should span full width in multi-column layouts */
27
- const WIDE_FIELD_TYPES = new Set([
28
- 'textarea',
29
- 'markdown',
30
- 'html',
31
- 'grid',
32
- 'rich-text',
33
- 'field:textarea',
34
- 'field:markdown',
35
- 'field:html',
36
- 'field:grid',
37
- 'field:rich-text',
38
- ]);
39
-
40
- /**
41
- * Check if a field type is "wide" (should span full row in multi-column layout).
42
- */
43
- export function isWideFieldType(type: string): boolean {
44
- return WIDE_FIELD_TYPES.has(type);
45
- }
46
-
47
- /**
48
- * Infer optimal number of columns for a detail section based on field count.
49
- * When containerWidth is provided, limits columns for narrower viewports.
50
- *
51
- * Rules (field-count based):
52
- * - 0-3 fields → 1 column
53
- * - 4-10 fields → 2 columns
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
60
- */
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;
74
- }
75
-
76
- /**
77
- * Apply auto span to wide fields so they span the full row.
78
- * Only sets span if the field does not already have one explicitly set.
79
- *
80
- * @returns A new array of fields with span applied where needed.
81
- */
82
- export function applyAutoSpan(
83
- fields: DetailViewField[],
84
- columns: number
85
- ): DetailViewField[] {
86
- if (columns <= 1) return fields;
87
-
88
- return fields.map((field) => {
89
- // User-defined span takes priority
90
- if (field.span !== undefined) return field;
91
-
92
- // Wide field types should span full row
93
- if (field.type && isWideFieldType(field.type)) {
94
- return { ...field, span: columns };
95
- }
96
-
97
- return field;
98
- });
99
- }
100
-
101
- /**
102
- * Main auto-layout orchestrator for detail sections.
103
- * Applies intelligent defaults only when the user has not explicitly configured columns.
104
- *
105
- * @param fields - The section fields
106
- * @param schemaColumns - User-provided columns (from DetailViewSection or DetailViewSchema)
107
- * @param containerWidth - Optional container width in px for responsive column capping
108
- * @returns Object with processed fields and inferred columns
109
- */
110
- export function applyDetailAutoLayout(
111
- fields: DetailViewField[],
112
- schemaColumns: number | undefined,
113
- containerWidth?: number
114
- ): { fields: DetailViewField[]; columns: number } {
115
- // If user explicitly set columns, respect it but still apply auto span
116
- if (schemaColumns !== undefined) {
117
- const processed = applyAutoSpan(fields, schemaColumns);
118
- return { fields: processed, columns: schemaColumns };
119
- }
120
-
121
- // Infer columns from field count (with optional container-width capping)
122
- const columns = inferDetailColumns(fields.length, containerWidth);
123
-
124
- // Apply auto span for wide fields
125
- const processed = applyAutoSpan(fields, columns);
126
-
127
- return { fields: processed, columns };
128
- }