@object-ui/components 4.8.0 → 5.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,258 @@
1
1
  # @object-ui/components
2
2
 
3
+ ## 5.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - bb2ea48: **Phase O.0 — fix: related-list shows wrong records (critical data bug)**
8
+
9
+ `RelatedList` previously called `dataSource.find(api)` with no filter
10
+ when auto-fetching, so every Related tab dumped the entire target
11
+ object table instead of the records that actually reference the
12
+ current parent (e.g. an Account showed every Contact in the system,
13
+ not only contacts of that account).
14
+
15
+ Two coupled fixes:
16
+ 1. `RelatedList` now requires `parentId` + `referenceField` to auto-
17
+ fetch. When both are present it calls `dataSource.find(api,
18
+ { $filter: { [referenceField]: parentId } })`. When either is
19
+ missing it renders the empty state and logs a developer warning —
20
+ never silently fetches the whole object.
21
+ 2. `RelatedCountStore` was sending the probe query as `{ where, limit }`
22
+ which most data-source adapters silently ignored (the codebase
23
+ convention is `{ $filter, $top }`). The tab-count badges were
24
+ therefore showing the global object count, not the parent-scoped
25
+ count. Switched to `$filter` / `$top` to match.
26
+
27
+ `record:related_list` renderer threads `ctx.recordId` through as
28
+ `parentId`; no schema author changes required.
29
+
30
+ **Breaking:** custom callers that depended on `RelatedList` fetching
31
+ the entire object table when `referenceField` is omitted will need to
32
+ either pass `data` explicitly or supply both `parentId` and
33
+ `referenceField`. The previous behaviour was a bug, not a feature.
34
+
35
+ ### Minor Changes
36
+
37
+ - 8930b15: feat(detail): close the gap between Page-assigned and default record detail pages (Track 1)
38
+
39
+ Custom Lightning-style record detail pages (assigned via `assignedPage` /
40
+ `Page` schemas) used to feel meaningfully poorer than the auto-generated
41
+ default detail view. They were missing cross-cutting affordances and
42
+ shipped with English-only tab labels and heavy bordered section cards
43
+ even when the host locale was Chinese. Track 1 closes the visible gap:
44
+ - **app-shell `RecordDetailView`**: the `assignedPage` branch now wears
45
+ the same chrome as the default branch — lifecycle managed-by badge
46
+ and presence avatars in the top-right, `MetadataPanel` debug panel,
47
+ `ActionConfirmDialog` / `ActionParamDialog`, and an auto-appended
48
+ `RecordChatterPanel` at the bottom of the page. Authors opt out of
49
+ the auto-discussion with `assignedPage.disableDiscussion = true`.
50
+ - **plugin-detail `record:details`**: defaults to `inlineEdit: true` so
51
+ fields are click-to-edit just like the default page, and synthesises
52
+ sections with `showBorder: false` by default so a Lightning page
53
+ doesn't double-wrap every block in a heavy Card.
54
+ - **components `page:tabs` / `page:accordion`**: well-known English
55
+ labels (Details / Related / Activity / History / Notes / Files /
56
+ Tasks / Events / Attachments / Chatter / Discussion / Comments /
57
+ Overview / Summary) auto-translate to Chinese (`zh-CN` / `zh-TW`)
58
+ via a built-in dictionary keyed off `document.documentElement.lang`.
59
+ Authors supplying explicit localised labels (string or
60
+ `{ default, zh-CN, ... }`) are not affected.
61
+ - **i18n provider**: applies the initial language to
62
+ `document.documentElement.lang` on mount (i18next does not fire
63
+ `languageChanged` for the bootstrap language), so locale-aware
64
+ renderers downstream see the right value from the first render.
65
+
66
+ - 95b6b21: feat(page:header): record-aware chip + dedupe registrations (Phase D)
67
+
68
+ The `page:header` schema renderer is the visual anchor of every custom
69
+ record detail page (lead, opportunity, future account/contact/case).
70
+ Before this change it had two problems that bled into every custom
71
+ page across the product:
72
+ 1. **Quadruple registration**: `@object-ui/layout` registered both
73
+ `page-header` and `page:header`, and `@object-ui/components`
74
+ independently registered `page:header` (and `page:section`).
75
+ Whichever package loaded last won the unqualified `page:header`
76
+ lookup — visually unstable.
77
+ 2. **Bare `<h1>`** with no record affordances (no icon, ★ favourite,
78
+ copy-id, edit, ⋯ menu) — every custom page shipped a thinner header
79
+ than the default detail view it was meant to supersede.
80
+
81
+ This commit:
82
+ - Removes the `@object-ui/layout` `page:header` registration. The
83
+ layout package keeps the legacy kebab-cased `page-header` alias only.
84
+ The canonical renderer now lives in `@object-ui/components` and is
85
+ always the one resolved.
86
+ - Upgrades `PageHeaderRenderer` to render a `<RecordTitleChip>` when
87
+ wrapped in a `RecordContext`. The chip mirrors the default detail
88
+ header: title (resolved from `data.name` / `data.title` /
89
+ `data.display_name`, or an interpolated `schema.title`), a favourite
90
+ star, the object label, and a copy-record-id button. Authors opt out
91
+ via `recordChrome: false` or hide individual affordances with
92
+ `showStar: false` / `showCopyId: false`.
93
+ - Extracts the chip into a new shared `RecordTitleChip` component in
94
+ `@object-ui/components/custom`. It carries an inline zh-CN/zh-TW
95
+ dictionary for star/copy tooltips so it stays i18n-correct without
96
+ pulling in a translation dependency.
97
+ - Fixes `interpolate()` so a `{account}`-style token that resolves to
98
+ a related-record object renders as empty instead of
99
+ `"[object Object]"`. Authors who want a field of the related record
100
+ should use a deeper path (`{account.name}`).
101
+
102
+ Verified at 1440×900 on `lead_detail` and `opportunity_detail`:
103
+ both pages now show the same chip with star + copy-id and the
104
+ opportunity highlights strip looks coherent with the chip above it.
105
+
106
+ - ddb08a7: feat(page:header,page:tabs): title fallback + single-tab strip auto-hide (Phase G slice 3 polish)
107
+ - `page:header.resolvedTitle` now honors `objectSchema.titleFormat`
108
+ (e.g. `{first_name} {last_name}`) and falls back through `name →
109
+ full_name → title → subject → display_name → label` before degrading
110
+ to `${objectLabel} ${idPrefix}`. Mirrors `DetailView.resolveDisplayTitle`
111
+ so default and synthesized record pages produce identical titles.
112
+ - `page:tabs` hides the tab strip entirely when there's only one tab
113
+ (a single labelled pill is visual clutter, not an affordance).
114
+ Authors can opt back in with `properties.alwaysShowStrip: true`.
115
+ Single-tab content margin tightens from `mt-3` to `mt-0` to remove
116
+ the now-empty top space.
117
+
118
+ - 927187a: Phase N.1 + N.2: visual polish for record detail pages.
119
+
120
+ **N.1 — System actions on full Lightning pages.** `PageHeaderRenderer`
121
+ now merges `headerSystemActions` from `RecordContext` with authored
122
+ actions (authored wins on name/id collision), so full custom pages
123
+ (lead, opportunity, ...) once again show 编辑 / 分享 / 删除 alongside
124
+ their authored actions. `sys_share` and `sys_delete` now use the
125
+ `outline` variant instead of `destructive` to read better in
126
+ multi-button clusters.
127
+
128
+ **N.2 — Hide empty fields by default in synth detail pages.**
129
+ `record:details` defaults `section.hideEmpty` to `true` so synthesized
130
+ pages don't render label graveyards on first load. The "显示 N 个空字段"
131
+ reveal toggle is preserved as the user-facing escape hatch. Authors can
132
+ opt back into showing every field by setting `hideEmpty: false` on the
133
+ section schema.
134
+
135
+ - bae8ba8: Phase N.3 + N.4 + N.6: record detail visual polish.
136
+
137
+ **N.3 — Highlight strip packs left.** `HeaderHighlight` no longer
138
+ stretches a 1-2 chip strip across the full page. Each cell is now
139
+ `min-w-[8rem] max-w-[16rem]` and wraps via flexbox so sparse strips
140
+ sit naturally at the left edge.
141
+
142
+ **N.4 — De-duplicate highlight ↔ body.** `record:details` accepts a
143
+ new `hideFields: string[]` prop. The synth pipeline auto-populates it
144
+ with the highlight-strip field list so a field surfaced in
145
+ `record:highlights` no longer appears a second time in the section
146
+ grid below. Authors can also set it directly on the schema.
147
+
148
+ **N.6 — Tab count badges only show when >0.** `page:tabs` suppresses
149
+ the count pill when the count is exactly 0 (was rendering "0" as a
150
+ muted badge on every empty Activity/History tab).
151
+
152
+ - b14fe09: Phase P.0 + P.5: tighten record-detail header chrome.
153
+ - `RecordTitleChip` collapses the title row to a single baseline-aligned line — H1, eyebrow object label, copy-id, favorite star — instead of the previous two-row title + subtitle layout.
154
+ - `record:details` extends the highlight-field dedup set to also exclude the title field resolved from `objectSchema.primaryField` (or the standard `name`/`full_name`/`title`/`subject`/`display_name`/`label` fallbacks). Removes the duplicate row that previously echoed the H1 (e.g. "客户名称: Acme Corporation") inside the field grid.
155
+
156
+ - a7bef6e: Phase P.3: anchor `page:tabs` 'line' variant with a proper underline rail.
157
+
158
+ The Shadcn Tabs primitive defaults to a pill-card look (bg-muted,
159
+ rounded, white-on-active). On long record-detail pages this strip
160
+ floats unmoored — users scroll past it without realising it's a
161
+ section anchor.
162
+
163
+ `PageTabsRenderer` now applies an underline-style treatment to the
164
+ default 'line' variant: the `TabsList` gets a bottom border, and each
165
+ `TabsTrigger` renders as a transparent button with a 2px primary-color
166
+ underline when active. 'card' and 'pill' variants are unchanged.
167
+
168
+ - 74962b0: feat(detail): record:discussion schema component + flush accordion variant
169
+ - New `record:discussion` schema type lets authors place the record
170
+ chatter feed anywhere in a custom Page schema. Wired through a
171
+ shared `DiscussionContext` provider on the `assignedPage` branch
172
+ of `RecordDetailView`; auto-append still applies when no explicit
173
+ `record:discussion` / `record:chatter` node is present.
174
+ - `page:accordion` gains a `variant` prop. Default `flush` strips the
175
+ per-item border so accordion sections no longer double-wrap inner
176
+ Card-bearing renderers (RelatedList, etc.). Authors who want the
177
+ old visual pass `variant: 'card'`.
178
+ - `translateLabel` now handles compound labels split by `&`, `and`,
179
+ or `和` (e.g. `Notes & Attachments` → `备注与附件`).
180
+
181
+ - fa4c2cb: feat(detail): renderViaSchema opt-in routes default detail through SchemaRenderer (Track 3 Phase G slice 2)
182
+
183
+ When `?renderViaSchema=1` is in the URL, or `objectDef.detail.renderViaSchema === true`,
184
+ `RecordDetailView`'s no-assignedPage branch now synthesizes a canonical
185
+ Page schema (`page:header` → `record:highlights` → `record:path` →
186
+ `page:tabs(record:details)` → `record:discussion`) via
187
+ `buildDefaultPageSchema(objectDef, { sections, highlightFields })` and
188
+ renders it through the existing `<SchemaRenderer>` pipeline.
189
+
190
+ This means every object without a custom assigned page can opt in to
191
+ the same chrome (record-aware header chip, chevron path, flush
192
+ accordion, discussion slot) that custom Lightning pages already enjoy.
193
+
194
+ Changes:
195
+ - `buildDefaultPageSchema` now emits `page:tabs.items` (correct shape
196
+ for the renderer) rather than `tabs`.
197
+ - `PageHeaderRenderer.resolvedTitle` honors `objectSchema.primaryField`
198
+ before the legacy `name/title/display_name/label` fallbacks.
199
+ - `RecordDetailView` rebuilds the synthesized schema with
200
+ `detailSchema.sections` + `highlightFields` at render time so
201
+ `record:details` inherits the same field layout the legacy
202
+ `<DetailView>` would have produced.
203
+
204
+ Flag is intentionally off by default — flipping the default is a
205
+ separate explicit commit after empirical parity validation across
206
+ multiple objects. Known gaps tracked for slice 3: titleFormat
207
+ fallback for objects without `primaryField`, auto Activity / History
208
+ tabs, header-action buttons.
209
+
210
+ ### Patch Changes
211
+
212
+ - 765d50f: fix(components): strip dangling separators from interpolated record titles
213
+
214
+ `page:header` now post-processes the result of interpolating a record's
215
+ `titleFormat` through `cleanupTitleSeparators` so a missing field in the
216
+ template doesn't leave a trailing/leading connector.
217
+
218
+ Example: with `titleFormat: '{contract_number} - {name}'` and a contract
219
+ whose `name` is empty, the header was rendering `CTR-0001 -` (with a
220
+ dangling hyphen). It now renders `CTR-0001`. Also handles a missing
221
+ middle field (`A - - B` → `A - B`) and collapses whitespace runs.
222
+
223
+ Supports hyphen / em-dash / en-dash / middle-dot / colon / slash / pipe
224
+ connectors. Idempotent. Exported as `cleanupTitleSeparators` from the
225
+ containers module; covered by 10 new unit tests.
226
+
227
+ - 3154334: fix(components): render `page:header.actions` on custom detail pages
228
+
229
+ `PageHeaderRenderer` previously read `title`, `subtitle`, `breadcrumb`,
230
+ `showStar`, `showCopyId` but never the `actions` array. Authored
231
+ Lightning record pages embed action buttons directly on
232
+ `page:header` (e.g. Lead → "Convert Lead", Opportunity → "Clone
233
+ Opportunity"); these buttons silently disappeared.
234
+
235
+ The renderer now reads `schema.actions ?? schema.properties?.actions`,
236
+ filters by `locations.includes('record_header')` (default-include when
237
+ absent), evaluates `visible` / `hidden` predicates (boolean, string,
238
+ or `{ dialect, source }` shapes) against the live record via
239
+ `ExpressionEvaluator`, and dispatches clicks through the
240
+ `ActionProvider`'s shared runner — so `confirmText`, `successMessage`,
241
+ `refreshAfter`, `flow`, navigation and modal handlers all fire.
242
+
243
+ The `data-page-actions-slot` portal target is preserved as a fallback
244
+ when no actions are declared in schema.
245
+
246
+ - Updated dependencies [8930b15]
247
+ - Updated dependencies [927187a]
248
+ - Updated dependencies [8435860]
249
+ - Updated dependencies [74962b0]
250
+ - Updated dependencies [7213027]
251
+ - @object-ui/i18n@5.0.0
252
+ - @object-ui/react@5.0.0
253
+ - @object-ui/types@5.0.0
254
+ - @object-ui/core@5.0.0
255
+
3
256
  ## 4.8.0
4
257
 
5
258
  ### Patch Changes
package/dist/index.css CHANGED
@@ -22,6 +22,7 @@
22
22
  --color-yellow-50: oklch(98.7% 0.026 102.212);
23
23
  --color-yellow-100: oklch(97.3% 0.071 103.193);
24
24
  --color-yellow-300: oklch(90.5% 0.182 98.111);
25
+ --color-yellow-400: oklch(85.2% 0.199 91.936);
25
26
  --color-green-100: oklch(96.2% 0.044 156.743);
26
27
  --color-green-500: oklch(72.3% 0.219 149.579);
27
28
  --color-green-600: oklch(62.7% 0.194 149.214);
@@ -562,6 +563,9 @@
562
563
  .mr-2 {
563
564
  margin-right: calc(var(--spacing) * 2);
564
565
  }
566
+ .-mb-px {
567
+ margin-bottom: -1px;
568
+ }
565
569
  .mb-0 {
566
570
  margin-bottom: calc(var(--spacing) * 0);
567
571
  }
@@ -1068,6 +1072,9 @@
1068
1072
  .max-w-7xl {
1069
1073
  max-width: var(--container-7xl);
1070
1074
  }
1075
+ .max-w-\[16rem\] {
1076
+ max-width: 16rem;
1077
+ }
1071
1078
  .max-w-\[40\%\] {
1072
1079
  max-width: 40%;
1073
1080
  }
@@ -1520,6 +1527,9 @@
1520
1527
  margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
1521
1528
  }
1522
1529
  }
1530
+ .self-center {
1531
+ align-self: center;
1532
+ }
1523
1533
  .self-start {
1524
1534
  align-self: flex-start;
1525
1535
  }
@@ -1644,6 +1654,10 @@
1644
1654
  --tw-border-style: dashed;
1645
1655
  border-style: dashed;
1646
1656
  }
1657
+ .border-none {
1658
+ --tw-border-style: none;
1659
+ border-style: none;
1660
+ }
1647
1661
  .border-\(--color-border\) {
1648
1662
  border-color: var(--color-border);
1649
1663
  }
@@ -1949,6 +1963,9 @@
1949
1963
  .fill-red-500 {
1950
1964
  fill: var(--color-red-500);
1951
1965
  }
1966
+ .fill-yellow-400 {
1967
+ fill: var(--color-yellow-400);
1968
+ }
1952
1969
  .object-cover {
1953
1970
  object-fit: cover;
1954
1971
  }
@@ -2078,6 +2095,9 @@
2078
2095
  .pb-2 {
2079
2096
  padding-bottom: calc(var(--spacing) * 2);
2080
2097
  }
2098
+ .pb-2\.5 {
2099
+ padding-bottom: calc(var(--spacing) * 2.5);
2100
+ }
2081
2101
  .pb-3 {
2082
2102
  padding-bottom: calc(var(--spacing) * 3);
2083
2103
  }
@@ -2297,6 +2317,12 @@
2297
2317
  color: color-mix(in oklab, var(--color-muted-foreground) 50%, transparent);
2298
2318
  }
2299
2319
  }
2320
+ .text-muted-foreground\/70 {
2321
+ color: color-mix(in srgb, hsl(var(--muted-foreground)) 70%, transparent);
2322
+ @supports (color: color-mix(in lab, red, red)) {
2323
+ color: color-mix(in oklab, var(--color-muted-foreground) 70%, transparent);
2324
+ }
2325
+ }
2300
2326
  .text-popover-foreground {
2301
2327
  color: var(--color-popover-foreground);
2302
2328
  }
@@ -2336,6 +2362,9 @@
2336
2362
  .text-white {
2337
2363
  color: var(--color-white);
2338
2364
  }
2365
+ .text-yellow-400 {
2366
+ color: var(--color-yellow-400);
2367
+ }
2339
2368
  .uppercase {
2340
2369
  text-transform: uppercase;
2341
2370
  }
@@ -4013,16 +4042,32 @@
4013
4042
  border-width: 1px;
4014
4043
  }
4015
4044
  }
4045
+ .data-\[state\=active\]\:border-primary {
4046
+ &[data-state="active"] {
4047
+ border-color: var(--color-primary);
4048
+ }
4049
+ }
4016
4050
  .data-\[state\=active\]\:bg-background {
4017
4051
  &[data-state="active"] {
4018
4052
  background-color: var(--color-background);
4019
4053
  }
4020
4054
  }
4055
+ .data-\[state\=active\]\:bg-transparent {
4056
+ &[data-state="active"] {
4057
+ background-color: transparent;
4058
+ }
4059
+ }
4021
4060
  .data-\[state\=active\]\:text-foreground {
4022
4061
  &[data-state="active"] {
4023
4062
  color: var(--color-foreground);
4024
4063
  }
4025
4064
  }
4065
+ .data-\[state\=active\]\:shadow-none {
4066
+ &[data-state="active"] {
4067
+ --tw-shadow: 0 0 #0000;
4068
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
4069
+ }
4070
+ }
4026
4071
  .data-\[state\=active\]\:shadow-sm {
4027
4072
  &[data-state="active"] {
4028
4073
  --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
@@ -4791,6 +4836,12 @@
4791
4836
  text-align: left;
4792
4837
  }
4793
4838
  }
4839
+ .sm\:text-2xl {
4840
+ @media (width >= 40rem) {
4841
+ font-size: var(--text-2xl);
4842
+ line-height: var(--tw-leading, var(--text-2xl--line-height));
4843
+ }
4844
+ }
4794
4845
  .sm\:text-sm {
4795
4846
  @media (width >= 40rem) {
4796
4847
  font-size: var(--text-sm);