@object-ui/app-shell 4.0.7 → 4.0.9

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.
@@ -206,22 +206,66 @@ export function ReportView({ dataSource }) {
206
206
  // Map @objectstack/spec report format to @object-ui/types ReportSchema:
207
207
  // - 'label' → 'title'
208
208
  // - 'columns' (with 'field') → 'fields' (with 'name') + auto-generate 'sections'
209
+ // - Hydrate type/options/referenceTo from the bound object's field metadata
210
+ // so the type-aware cell renderer can show select badges, lookup links,
211
+ // boolean ✓/✗, email/url/phone links, etc. instead of raw values.
209
212
  const mapReportForViewer = (src) => {
210
213
  const mapped = { ...src };
211
214
  if (!mapped.title && mapped.label) {
212
215
  mapped.title = mapped.label;
213
216
  }
217
+ // Build a lookup of object-field metadata to hydrate column type info.
218
+ const objName = mapped.objectName || mapped.dataSource?.object || mapped.dataSource?.resource;
219
+ const objDef = objName ? objects?.find((o) => o.name === objName) : null;
220
+ const objFieldsArr = Array.isArray(objDef?.fields)
221
+ ? objDef.fields
222
+ : objDef?.fields
223
+ ? Object.entries(objDef.fields).map(([name, def]) => ({ name, ...def }))
224
+ : [];
225
+ const objFieldMap = {};
226
+ for (const f of objFieldsArr) {
227
+ if (f && f.name)
228
+ objFieldMap[f.name] = f;
229
+ }
230
+ const hydrate = (col) => {
231
+ const name = col.name || col.field;
232
+ const meta = name ? objFieldMap[name] : undefined;
233
+ if (!meta)
234
+ return col;
235
+ // Author-provided values win; only fill in what's missing.
236
+ const out = { ...col };
237
+ if (out.type === undefined && meta.type !== undefined)
238
+ out.type = meta.type;
239
+ if (out.options === undefined && Array.isArray(meta.options))
240
+ out.options = meta.options;
241
+ if (out.referenceTo === undefined) {
242
+ const ref = meta.referenceTo || meta.reference?.to || meta.target;
243
+ if (ref)
244
+ out.referenceTo = ref;
245
+ }
246
+ if (out.label === undefined && meta.label)
247
+ out.label = meta.label;
248
+ return out;
249
+ };
214
250
  // Map spec 'columns' (field/label/aggregate) → ReportSchema 'fields' (name/label/aggregation)
215
251
  if (!mapped.fields && Array.isArray(mapped.columns)) {
216
- mapped.fields = mapped.columns.map((col) => ({
217
- name: col.field || col.name,
218
- label: col.label,
219
- type: col.type,
220
- format: col.format,
221
- renderAs: col.renderAs,
222
- colorMap: col.colorMap,
223
- ...(col.aggregate ? { aggregation: col.aggregate, showInSummary: true } : {}),
224
- }));
252
+ mapped.fields = mapped.columns.map((col) => {
253
+ const hydrated = hydrate(col);
254
+ return {
255
+ name: hydrated.field || hydrated.name,
256
+ label: hydrated.label,
257
+ type: hydrated.type,
258
+ options: hydrated.options,
259
+ referenceTo: hydrated.referenceTo,
260
+ format: hydrated.format,
261
+ renderAs: hydrated.renderAs,
262
+ colorMap: hydrated.colorMap,
263
+ ...(hydrated.aggregate ? { aggregation: hydrated.aggregate, showInSummary: true } : {}),
264
+ };
265
+ });
266
+ }
267
+ else if (Array.isArray(mapped.fields)) {
268
+ mapped.fields = mapped.fields.map(hydrate);
225
269
  }
226
270
  // Always regenerate sections from current fields so that live config
227
271
  // changes (e.g. field picker updates) are immediately reflected in
@@ -229,7 +273,8 @@ export function ReportView({ dataSource }) {
229
273
  // did not update the rendered report.
230
274
  if (mapped.fields && Array.isArray(mapped.fields) && mapped.fields.length > 0) {
231
275
  const hasSummaryFields = mapped.fields.some((f) => f.showInSummary || f.aggregation);
232
- const reportType = mapped.reportType || 'tabular';
276
+ // Spec key is `type`; legacy renderer used `reportType`. Accept either.
277
+ const reportType = mapped.type || mapped.reportType || 'tabular';
233
278
  const sections = [];
234
279
  if (reportType === 'summary' || hasSummaryFields) {
235
280
  sections.push({ type: 'summary', title: 'Key Metrics' });
@@ -241,27 +286,34 @@ export function ReportView({ dataSource }) {
241
286
  name: f.name,
242
287
  label: f.label,
243
288
  type: f.type,
289
+ options: f.options,
290
+ referenceTo: f.referenceTo,
244
291
  format: f.format,
245
292
  renderAs: f.renderAs,
246
293
  colorMap: f.colorMap,
247
294
  })),
248
295
  });
249
- // Generate chart section from chartConfig if configured
250
- if (mapped.chartConfig?.chartType) {
296
+ // Generate chart section from chart config if configured.
297
+ // Spec keys: type / xAxis / yAxis. Legacy: chartType / xAxisField / yAxisFields[0].
298
+ const chartCfg = mapped.chart || mapped.chartConfig;
299
+ const chartTypeVal = chartCfg?.type || chartCfg?.chartType;
300
+ if (chartTypeVal) {
301
+ const xField = chartCfg.xAxis || chartCfg.xAxisField;
302
+ const yField = chartCfg.yAxis || chartCfg.yAxisFields?.[0];
251
303
  sections.push({
252
304
  type: 'chart',
253
305
  title: 'Chart',
254
306
  chart: {
255
307
  type: 'chart',
256
- chartType: mapped.chartConfig.chartType,
257
- xAxisField: mapped.chartConfig.xAxisField,
258
- yAxisFields: mapped.chartConfig.yAxisFields,
308
+ chartType: chartTypeVal,
309
+ xAxisField: xField,
310
+ yAxisFields: yField ? [yField] : chartCfg.yAxisFields,
259
311
  },
260
312
  });
261
313
  }
262
314
  // Preserve any user-defined chart sections from the original schema
263
315
  if (Array.isArray(src.sections)) {
264
- const chartSections = src.sections.filter((s) => s.type === 'chart' && !mapped.chartConfig?.chartType);
316
+ const chartSections = src.sections.filter((s) => s.type === 'chart' && !chartTypeVal);
265
317
  sections.push(...chartSections);
266
318
  }
267
319
  mapped.sections = sections;
@@ -12,13 +12,23 @@ import { jsx as _jsx } from "react/jsx-runtime";
12
12
  * All changes are buffered in a local draft state. Clicking Save commits
13
13
  * the draft via onSave; Discard resets to the original activeView.
14
14
  */
15
- import { useMemo, useEffect, useRef, useCallback } from 'react';
16
- import { ConfigPanelRenderer, useConfigDraft } from '@object-ui/components';
15
+ import { useMemo, useEffect, useRef, useCallback, useState } from 'react';
16
+ import { ConfigPanelRenderer, useConfigDraft, Button } from '@object-ui/components';
17
+ import { Settings2 } from 'lucide-react';
17
18
  import { useObjectTranslation } from '@object-ui/i18n';
18
19
  import { buildViewConfigSchema, deriveFieldOptions, toFilterGroup, toSortItems, VIEW_TYPE_LABELS, } from '@object-ui/plugin-view';
19
20
  export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, objectDef, onViewUpdate, onSave, onCreate }) {
20
21
  const { t } = useObjectTranslation();
21
22
  const panelRef = useRef(null);
23
+ // "Show advanced settings" — when false (default), the panel only shows
24
+ // the Airtable-essential subset. When true, every section/field is
25
+ // surfaced for power users. Reset whenever the panel closes so reopening
26
+ // returns to the simplified view.
27
+ const [showAdvanced, setShowAdvanced] = useState(false);
28
+ useEffect(() => {
29
+ if (!open)
30
+ setShowAdvanced(false);
31
+ }, [open]);
22
32
  // Default empty view for create mode
23
33
  const defaultNewView = useMemo(() => ({
24
34
  id: `view_${Date.now()}`,
@@ -53,7 +63,8 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
53
63
  // Bridge: filter/sort → builder format
54
64
  const filterGroupValue = useMemo(() => toFilterGroup(draft.filter), [draft.filter]);
55
65
  const sortItemsValue = useMemo(() => toSortItems(draft.sort), [draft.sort]);
56
- // Build schema always essentials-only (Airtable-style focused layout).
66
+ // Build schema. essentialOnly is the default; the user can opt-in to the
67
+ // full advanced surface via the "Show advanced settings" toggle below.
57
68
  const schema = useMemo(() => buildViewConfigSchema({
58
69
  t,
59
70
  fieldOptions,
@@ -61,8 +72,8 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
61
72
  updateField,
62
73
  filterGroupValue,
63
74
  sortItemsValue,
64
- essentialOnly: true,
65
- }), [t, fieldOptions, objectDef, updateField, filterGroupValue, sortItemsValue]);
75
+ essentialOnly: !showAdvanced,
76
+ }), [t, fieldOptions, objectDef, updateField, filterGroupValue, sortItemsValue, showAdvanced]);
66
77
  // Override breadcrumb with dynamic view type
67
78
  const viewType = draft.type || 'grid';
68
79
  const dynamicSchema = useMemo(() => ({
@@ -90,5 +101,12 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
90
101
  const panelTitle = mode === 'create'
91
102
  ? t('console.objectView.createView')
92
103
  : t('console.objectView.configureView');
93
- return (_jsx(ConfigPanelRenderer, { open: open, onClose: onClose, schema: dynamicSchema, draft: draft, isDirty: isDirty, onFieldChange: updateField, onSave: handleSave, onDiscard: handleDiscard, onUndo: undo, onRedo: redo, canUndo: canUndo, canRedo: canRedo, undoLabel: t('designer.undo'), redoLabel: t('designer.redo'), objectDef: objectDef, saveLabel: t('console.objectView.save'), discardLabel: t('console.objectView.discard'), panelRef: panelRef, role: "complementary", ariaLabel: panelTitle, tabIndex: -1, testId: "view-config-panel", closeTitle: t('console.objectView.closePanel'), footerTestId: "view-config-footer", saveTestId: "view-config-save", discardTestId: "view-config-discard", className: "transition-all overflow-hidden" }));
104
+ // Header-extra: "Show advanced" toggle button. Subtle gear icon button
105
+ // sitting next to undo/redo/close — clearly affordant but not visually
106
+ // dominant. Title swaps between "Show advanced settings" and "Show fewer
107
+ // settings" so the user always knows what the next click will do.
108
+ const advancedToggle = (_jsx(Button, { size: "sm", variant: showAdvanced ? 'secondary' : 'ghost', onClick: () => setShowAdvanced(v => !v), className: "h-7 w-7 p-0", "data-testid": "view-config-advanced-toggle", "aria-pressed": showAdvanced, title: showAdvanced
109
+ ? t('console.objectView.showFewerSettings', { defaultValue: 'Show fewer settings' })
110
+ : t('console.objectView.showAdvancedSettings', { defaultValue: 'Show advanced settings' }), children: _jsx(Settings2, { className: "h-3.5 w-3.5" }) }));
111
+ return (_jsx(ConfigPanelRenderer, { open: open, onClose: onClose, schema: dynamicSchema, draft: draft, isDirty: isDirty, onFieldChange: updateField, onSave: handleSave, onDiscard: handleDiscard, onUndo: undo, onRedo: redo, canUndo: canUndo, canRedo: canRedo, undoLabel: t('designer.undo'), redoLabel: t('designer.redo'), objectDef: objectDef, saveLabel: t('console.objectView.save'), discardLabel: t('console.objectView.discard'), panelRef: panelRef, role: "complementary", ariaLabel: panelTitle, tabIndex: -1, testId: "view-config-panel", closeTitle: t('console.objectView.closePanel'), footerTestId: "view-config-footer", saveTestId: "view-config-save", discardTestId: "view-config-discard", headerExtra: advancedToggle, className: "transition-all overflow-hidden" }));
94
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/app-shell",
3
- "version": "4.0.7",
3
+ "version": "4.0.9",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
@@ -27,45 +27,45 @@
27
27
  "dependencies": {
28
28
  "lucide-react": "^1.14.0",
29
29
  "sonner": "^2.0.7",
30
- "@object-ui/auth": "4.0.7",
31
- "@object-ui/collaboration": "4.0.7",
32
- "@object-ui/components": "4.0.7",
33
- "@object-ui/core": "4.0.7",
34
- "@object-ui/data-objectstack": "4.0.7",
35
- "@object-ui/fields": "4.0.7",
36
- "@object-ui/i18n": "4.0.7",
37
- "@object-ui/layout": "4.0.7",
38
- "@object-ui/permissions": "4.0.7",
39
- "@object-ui/react": "4.0.7",
40
- "@object-ui/types": "4.0.7"
30
+ "@object-ui/auth": "4.0.9",
31
+ "@object-ui/collaboration": "4.0.9",
32
+ "@object-ui/core": "4.0.9",
33
+ "@object-ui/data-objectstack": "4.0.9",
34
+ "@object-ui/fields": "4.0.9",
35
+ "@object-ui/i18n": "4.0.9",
36
+ "@object-ui/layout": "4.0.9",
37
+ "@object-ui/permissions": "4.0.9",
38
+ "@object-ui/react": "4.0.9",
39
+ "@object-ui/types": "4.0.9",
40
+ "@object-ui/components": "4.0.9"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "react": "^18.0.0 || ^19.0.0",
44
44
  "react-dom": "^18.0.0 || ^19.0.0",
45
45
  "react-router-dom": "^6.0.0 || ^7.0.0",
46
- "@object-ui/plugin-calendar": "4.0.7",
47
- "@object-ui/plugin-charts": "4.0.7",
48
- "@object-ui/plugin-chatbot": "4.0.7",
49
- "@object-ui/plugin-dashboard": "4.0.7",
50
- "@object-ui/plugin-designer": "4.0.7",
51
- "@object-ui/plugin-detail": "4.0.7",
52
- "@object-ui/plugin-form": "4.0.7",
53
- "@object-ui/plugin-grid": "4.0.7",
54
- "@object-ui/plugin-kanban": "4.0.7",
55
- "@object-ui/plugin-list": "4.0.7",
56
- "@object-ui/plugin-report": "4.0.7",
57
- "@object-ui/plugin-view": "4.0.7"
46
+ "@object-ui/plugin-calendar": "4.0.9",
47
+ "@object-ui/plugin-charts": "4.0.9",
48
+ "@object-ui/plugin-chatbot": "4.0.9",
49
+ "@object-ui/plugin-dashboard": "4.0.9",
50
+ "@object-ui/plugin-designer": "4.0.9",
51
+ "@object-ui/plugin-detail": "4.0.9",
52
+ "@object-ui/plugin-form": "4.0.9",
53
+ "@object-ui/plugin-grid": "4.0.9",
54
+ "@object-ui/plugin-kanban": "4.0.9",
55
+ "@object-ui/plugin-list": "4.0.9",
56
+ "@object-ui/plugin-report": "4.0.9",
57
+ "@object-ui/plugin-view": "4.0.9"
58
58
  },
59
59
  "devDependencies": {
60
- "@types/node": "^25.6.0",
60
+ "@types/node": "^25.7.0",
61
61
  "@types/react": "19.2.14",
62
62
  "@types/react-dom": "19.2.3",
63
- "react": "19.2.5",
64
- "react-dom": "19.2.5",
65
- "react-router-dom": "^7.14.2",
63
+ "react": "19.2.6",
64
+ "react-dom": "19.2.6",
65
+ "react-router-dom": "^7.15.0",
66
66
  "sonner": "^2.0.7",
67
67
  "typescript": "^6.0.3",
68
- "vite": "^8.0.10"
68
+ "vite": "^8.0.12"
69
69
  },
70
70
  "keywords": [
71
71
  "objectui",