@object-ui/components 4.8.0 → 5.0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,267 @@
1
1
  # @object-ui/components
2
2
 
3
+ ## 5.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @object-ui/types@5.0.1
8
+ - @object-ui/core@5.0.1
9
+ - @object-ui/i18n@5.0.1
10
+ - @object-ui/react@5.0.1
11
+
12
+ ## 5.0.0
13
+
14
+ ### Major Changes
15
+
16
+ - bb2ea48: **Phase O.0 — fix: related-list shows wrong records (critical data bug)**
17
+
18
+ `RelatedList` previously called `dataSource.find(api)` with no filter
19
+ when auto-fetching, so every Related tab dumped the entire target
20
+ object table instead of the records that actually reference the
21
+ current parent (e.g. an Account showed every Contact in the system,
22
+ not only contacts of that account).
23
+
24
+ Two coupled fixes:
25
+ 1. `RelatedList` now requires `parentId` + `referenceField` to auto-
26
+ fetch. When both are present it calls `dataSource.find(api,
27
+ { $filter: { [referenceField]: parentId } })`. When either is
28
+ missing it renders the empty state and logs a developer warning —
29
+ never silently fetches the whole object.
30
+ 2. `RelatedCountStore` was sending the probe query as `{ where, limit }`
31
+ which most data-source adapters silently ignored (the codebase
32
+ convention is `{ $filter, $top }`). The tab-count badges were
33
+ therefore showing the global object count, not the parent-scoped
34
+ count. Switched to `$filter` / `$top` to match.
35
+
36
+ `record:related_list` renderer threads `ctx.recordId` through as
37
+ `parentId`; no schema author changes required.
38
+
39
+ **Breaking:** custom callers that depended on `RelatedList` fetching
40
+ the entire object table when `referenceField` is omitted will need to
41
+ either pass `data` explicitly or supply both `parentId` and
42
+ `referenceField`. The previous behaviour was a bug, not a feature.
43
+
44
+ ### Minor Changes
45
+
46
+ - 8930b15: feat(detail): close the gap between Page-assigned and default record detail pages (Track 1)
47
+
48
+ Custom Lightning-style record detail pages (assigned via `assignedPage` /
49
+ `Page` schemas) used to feel meaningfully poorer than the auto-generated
50
+ default detail view. They were missing cross-cutting affordances and
51
+ shipped with English-only tab labels and heavy bordered section cards
52
+ even when the host locale was Chinese. Track 1 closes the visible gap:
53
+ - **app-shell `RecordDetailView`**: the `assignedPage` branch now wears
54
+ the same chrome as the default branch — lifecycle managed-by badge
55
+ and presence avatars in the top-right, `MetadataPanel` debug panel,
56
+ `ActionConfirmDialog` / `ActionParamDialog`, and an auto-appended
57
+ `RecordChatterPanel` at the bottom of the page. Authors opt out of
58
+ the auto-discussion with `assignedPage.disableDiscussion = true`.
59
+ - **plugin-detail `record:details`**: defaults to `inlineEdit: true` so
60
+ fields are click-to-edit just like the default page, and synthesises
61
+ sections with `showBorder: false` by default so a Lightning page
62
+ doesn't double-wrap every block in a heavy Card.
63
+ - **components `page:tabs` / `page:accordion`**: well-known English
64
+ labels (Details / Related / Activity / History / Notes / Files /
65
+ Tasks / Events / Attachments / Chatter / Discussion / Comments /
66
+ Overview / Summary) auto-translate to Chinese (`zh-CN` / `zh-TW`)
67
+ via a built-in dictionary keyed off `document.documentElement.lang`.
68
+ Authors supplying explicit localised labels (string or
69
+ `{ default, zh-CN, ... }`) are not affected.
70
+ - **i18n provider**: applies the initial language to
71
+ `document.documentElement.lang` on mount (i18next does not fire
72
+ `languageChanged` for the bootstrap language), so locale-aware
73
+ renderers downstream see the right value from the first render.
74
+
75
+ - 95b6b21: feat(page:header): record-aware chip + dedupe registrations (Phase D)
76
+
77
+ The `page:header` schema renderer is the visual anchor of every custom
78
+ record detail page (lead, opportunity, future account/contact/case).
79
+ Before this change it had two problems that bled into every custom
80
+ page across the product:
81
+ 1. **Quadruple registration**: `@object-ui/layout` registered both
82
+ `page-header` and `page:header`, and `@object-ui/components`
83
+ independently registered `page:header` (and `page:section`).
84
+ Whichever package loaded last won the unqualified `page:header`
85
+ lookup — visually unstable.
86
+ 2. **Bare `<h1>`** with no record affordances (no icon, ★ favourite,
87
+ copy-id, edit, ⋯ menu) — every custom page shipped a thinner header
88
+ than the default detail view it was meant to supersede.
89
+
90
+ This commit:
91
+ - Removes the `@object-ui/layout` `page:header` registration. The
92
+ layout package keeps the legacy kebab-cased `page-header` alias only.
93
+ The canonical renderer now lives in `@object-ui/components` and is
94
+ always the one resolved.
95
+ - Upgrades `PageHeaderRenderer` to render a `<RecordTitleChip>` when
96
+ wrapped in a `RecordContext`. The chip mirrors the default detail
97
+ header: title (resolved from `data.name` / `data.title` /
98
+ `data.display_name`, or an interpolated `schema.title`), a favourite
99
+ star, the object label, and a copy-record-id button. Authors opt out
100
+ via `recordChrome: false` or hide individual affordances with
101
+ `showStar: false` / `showCopyId: false`.
102
+ - Extracts the chip into a new shared `RecordTitleChip` component in
103
+ `@object-ui/components/custom`. It carries an inline zh-CN/zh-TW
104
+ dictionary for star/copy tooltips so it stays i18n-correct without
105
+ pulling in a translation dependency.
106
+ - Fixes `interpolate()` so a `{account}`-style token that resolves to
107
+ a related-record object renders as empty instead of
108
+ `"[object Object]"`. Authors who want a field of the related record
109
+ should use a deeper path (`{account.name}`).
110
+
111
+ Verified at 1440×900 on `lead_detail` and `opportunity_detail`:
112
+ both pages now show the same chip with star + copy-id and the
113
+ opportunity highlights strip looks coherent with the chip above it.
114
+
115
+ - ddb08a7: feat(page:header,page:tabs): title fallback + single-tab strip auto-hide (Phase G slice 3 polish)
116
+ - `page:header.resolvedTitle` now honors `objectSchema.titleFormat`
117
+ (e.g. `{first_name} {last_name}`) and falls back through `name →
118
+ full_name → title → subject → display_name → label` before degrading
119
+ to `${objectLabel} ${idPrefix}`. Mirrors `DetailView.resolveDisplayTitle`
120
+ so default and synthesized record pages produce identical titles.
121
+ - `page:tabs` hides the tab strip entirely when there's only one tab
122
+ (a single labelled pill is visual clutter, not an affordance).
123
+ Authors can opt back in with `properties.alwaysShowStrip: true`.
124
+ Single-tab content margin tightens from `mt-3` to `mt-0` to remove
125
+ the now-empty top space.
126
+
127
+ - 927187a: Phase N.1 + N.2: visual polish for record detail pages.
128
+
129
+ **N.1 — System actions on full Lightning pages.** `PageHeaderRenderer`
130
+ now merges `headerSystemActions` from `RecordContext` with authored
131
+ actions (authored wins on name/id collision), so full custom pages
132
+ (lead, opportunity, ...) once again show 编辑 / 分享 / 删除 alongside
133
+ their authored actions. `sys_share` and `sys_delete` now use the
134
+ `outline` variant instead of `destructive` to read better in
135
+ multi-button clusters.
136
+
137
+ **N.2 — Hide empty fields by default in synth detail pages.**
138
+ `record:details` defaults `section.hideEmpty` to `true` so synthesized
139
+ pages don't render label graveyards on first load. The "显示 N 个空字段"
140
+ reveal toggle is preserved as the user-facing escape hatch. Authors can
141
+ opt back into showing every field by setting `hideEmpty: false` on the
142
+ section schema.
143
+
144
+ - bae8ba8: Phase N.3 + N.4 + N.6: record detail visual polish.
145
+
146
+ **N.3 — Highlight strip packs left.** `HeaderHighlight` no longer
147
+ stretches a 1-2 chip strip across the full page. Each cell is now
148
+ `min-w-[8rem] max-w-[16rem]` and wraps via flexbox so sparse strips
149
+ sit naturally at the left edge.
150
+
151
+ **N.4 — De-duplicate highlight ↔ body.** `record:details` accepts a
152
+ new `hideFields: string[]` prop. The synth pipeline auto-populates it
153
+ with the highlight-strip field list so a field surfaced in
154
+ `record:highlights` no longer appears a second time in the section
155
+ grid below. Authors can also set it directly on the schema.
156
+
157
+ **N.6 — Tab count badges only show when >0.** `page:tabs` suppresses
158
+ the count pill when the count is exactly 0 (was rendering "0" as a
159
+ muted badge on every empty Activity/History tab).
160
+
161
+ - b14fe09: Phase P.0 + P.5: tighten record-detail header chrome.
162
+ - `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.
163
+ - `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.
164
+
165
+ - a7bef6e: Phase P.3: anchor `page:tabs` 'line' variant with a proper underline rail.
166
+
167
+ The Shadcn Tabs primitive defaults to a pill-card look (bg-muted,
168
+ rounded, white-on-active). On long record-detail pages this strip
169
+ floats unmoored — users scroll past it without realising it's a
170
+ section anchor.
171
+
172
+ `PageTabsRenderer` now applies an underline-style treatment to the
173
+ default 'line' variant: the `TabsList` gets a bottom border, and each
174
+ `TabsTrigger` renders as a transparent button with a 2px primary-color
175
+ underline when active. 'card' and 'pill' variants are unchanged.
176
+
177
+ - 74962b0: feat(detail): record:discussion schema component + flush accordion variant
178
+ - New `record:discussion` schema type lets authors place the record
179
+ chatter feed anywhere in a custom Page schema. Wired through a
180
+ shared `DiscussionContext` provider on the `assignedPage` branch
181
+ of `RecordDetailView`; auto-append still applies when no explicit
182
+ `record:discussion` / `record:chatter` node is present.
183
+ - `page:accordion` gains a `variant` prop. Default `flush` strips the
184
+ per-item border so accordion sections no longer double-wrap inner
185
+ Card-bearing renderers (RelatedList, etc.). Authors who want the
186
+ old visual pass `variant: 'card'`.
187
+ - `translateLabel` now handles compound labels split by `&`, `and`,
188
+ or `和` (e.g. `Notes & Attachments` → `备注与附件`).
189
+
190
+ - fa4c2cb: feat(detail): renderViaSchema opt-in routes default detail through SchemaRenderer (Track 3 Phase G slice 2)
191
+
192
+ When `?renderViaSchema=1` is in the URL, or `objectDef.detail.renderViaSchema === true`,
193
+ `RecordDetailView`'s no-assignedPage branch now synthesizes a canonical
194
+ Page schema (`page:header` → `record:highlights` → `record:path` →
195
+ `page:tabs(record:details)` → `record:discussion`) via
196
+ `buildDefaultPageSchema(objectDef, { sections, highlightFields })` and
197
+ renders it through the existing `<SchemaRenderer>` pipeline.
198
+
199
+ This means every object without a custom assigned page can opt in to
200
+ the same chrome (record-aware header chip, chevron path, flush
201
+ accordion, discussion slot) that custom Lightning pages already enjoy.
202
+
203
+ Changes:
204
+ - `buildDefaultPageSchema` now emits `page:tabs.items` (correct shape
205
+ for the renderer) rather than `tabs`.
206
+ - `PageHeaderRenderer.resolvedTitle` honors `objectSchema.primaryField`
207
+ before the legacy `name/title/display_name/label` fallbacks.
208
+ - `RecordDetailView` rebuilds the synthesized schema with
209
+ `detailSchema.sections` + `highlightFields` at render time so
210
+ `record:details` inherits the same field layout the legacy
211
+ `<DetailView>` would have produced.
212
+
213
+ Flag is intentionally off by default — flipping the default is a
214
+ separate explicit commit after empirical parity validation across
215
+ multiple objects. Known gaps tracked for slice 3: titleFormat
216
+ fallback for objects without `primaryField`, auto Activity / History
217
+ tabs, header-action buttons.
218
+
219
+ ### Patch Changes
220
+
221
+ - 765d50f: fix(components): strip dangling separators from interpolated record titles
222
+
223
+ `page:header` now post-processes the result of interpolating a record's
224
+ `titleFormat` through `cleanupTitleSeparators` so a missing field in the
225
+ template doesn't leave a trailing/leading connector.
226
+
227
+ Example: with `titleFormat: '{contract_number} - {name}'` and a contract
228
+ whose `name` is empty, the header was rendering `CTR-0001 -` (with a
229
+ dangling hyphen). It now renders `CTR-0001`. Also handles a missing
230
+ middle field (`A - - B` → `A - B`) and collapses whitespace runs.
231
+
232
+ Supports hyphen / em-dash / en-dash / middle-dot / colon / slash / pipe
233
+ connectors. Idempotent. Exported as `cleanupTitleSeparators` from the
234
+ containers module; covered by 10 new unit tests.
235
+
236
+ - 3154334: fix(components): render `page:header.actions` on custom detail pages
237
+
238
+ `PageHeaderRenderer` previously read `title`, `subtitle`, `breadcrumb`,
239
+ `showStar`, `showCopyId` but never the `actions` array. Authored
240
+ Lightning record pages embed action buttons directly on
241
+ `page:header` (e.g. Lead → "Convert Lead", Opportunity → "Clone
242
+ Opportunity"); these buttons silently disappeared.
243
+
244
+ The renderer now reads `schema.actions ?? schema.properties?.actions`,
245
+ filters by `locations.includes('record_header')` (default-include when
246
+ absent), evaluates `visible` / `hidden` predicates (boolean, string,
247
+ or `{ dialect, source }` shapes) against the live record via
248
+ `ExpressionEvaluator`, and dispatches clicks through the
249
+ `ActionProvider`'s shared runner — so `confirmText`, `successMessage`,
250
+ `refreshAfter`, `flow`, navigation and modal handlers all fire.
251
+
252
+ The `data-page-actions-slot` portal target is preserved as a fallback
253
+ when no actions are declared in schema.
254
+
255
+ - Updated dependencies [8930b15]
256
+ - Updated dependencies [927187a]
257
+ - Updated dependencies [8435860]
258
+ - Updated dependencies [74962b0]
259
+ - Updated dependencies [7213027]
260
+ - @object-ui/i18n@5.0.0
261
+ - @object-ui/react@5.0.0
262
+ - @object-ui/types@5.0.0
263
+ - @object-ui/core@5.0.0
264
+
3
265
  ## 4.8.0
4
266
 
5
267
  ### 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);