Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/8654-timeline-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@object-ui/plugin-timeline': minor
---

`navigation` is declared on the `object-timeline` block, by reference to `@objectstack/spec` (objectui#8654). This is the timeline arm of the objectui#8652 maintainer ruling: the spec declares the key on the `object-timeline` element from 17.5.0, for the block standalone, and objectui now mirrors it.

- `ObjectTimelineProps['schema']` declares `navigation` with the spec's `NavigationConfig` type (`ViewNavigationConfig`), the type the grid, kanban and calendar schemas use. `ObjectTimeline` reads the key without a cast. `TimelineSchema` in `@object-ui/types` is unchanged: it describes the presentational rail, which does not read the key.
- The `object-timeline` and `view:timeline` registrations publish `navigation` in their `inputs`, so the designer offers it.
- An authored `{ "type": "object-timeline", "properties": { … } }` document was already judged by the spec row: a bad `mode` is refused at `properties.navigation.mode`, and a member the spec does not declare inside the block is refused. That is unchanged and now pinned.

What a click does with each member is unchanged. `drawer`, `modal` and `popover` open the entry's record in that overlay, and `new_window` or `openNewTab: true` opens the record page in a new tab. Three things open nothing on a timeline that no parent view navigates for: an absent key, `page` (also what a block without `mode` resolves to), and `split`. The published input description says so.
42 changes: 42 additions & 0 deletions content/docs/plugins/plugin-timeline.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,48 @@ writing one is a type error and a validation failure. Write `items` instead of
the vertical rail is always drawn on the left.
</Callout>

## Object-bound timeline (`object-timeline`)

`object-timeline` fetches records from `objectName` and draws one entry per
record, from the fields its `timeline` config names. An authored node takes its
props in the `properties` bag, where `@objectstack/spec`'s
`ComponentPropsMap['object-timeline']` row declares them:

```json
{
"type": "object-timeline",
"properties": {
"objectName": "project_task",
"timeline": { "startDateField": "start_date", "titleField": "name" },
"navigation": { "mode": "drawer" }
}
}
```

### Entry-click navigation

`navigation` is what an entry click opens — the spec's `NavigationConfig` by
reference, `ViewNavigationConfig` in `@object-ui/types`, the type
`ObjectGridSchema.navigation` uses: `mode` (`page`, `drawer`, `modal`, `split`,
`popover`, `new_window` or `none`) with `size`, `openNewTab` and
`preventNavigation`.

| Member | On an entry click |
|--------|-------------------|
| `mode: 'drawer'` / `'modal'` / `'popover'` | Opens the entry's record in that overlay |
| `mode: 'new_window'`, or `openNewTab: true` | Opens the record page in a new tab; `openNewTab` outranks the mode |
| `preventNavigation: true`, or `mode: 'none'` | Opens nothing |
| `size` | Sets the overlay width (`sm` … `full`, or `auto` for the default) |

A click handler from a parent view outranks the whole key.

<Callout type="warn">
Write the block with `mode`. With the key absent, or with a block written
without `mode` (it takes the spec's `page` default), a click opens nothing on a
timeline no parent view navigates for (objectui#11293). `split` opens nothing on
this block either, because the timeline gives the split shell no main panel.
</Callout>

## Variants

### Marker Variants
Expand Down
31 changes: 31 additions & 0 deletions packages/plugin-timeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,37 @@ const schema = {
};
```

### Object-bound timeline (`object-timeline`)

`object-timeline` fetches records from `objectName` and draws one entry per
record, from the fields its `timeline` config names. An authored node takes its
props in the `properties` bag, where `@objectstack/spec`'s
`ComponentPropsMap['object-timeline']` row declares them:

```json
{
"type": "object-timeline",
"properties": {
"objectName": "project_task",
"timeline": { "startDateField": "start_date", "titleField": "name" },
"navigation": { "mode": "drawer" }
}
}
```

`navigation` is what an entry click opens — the spec's `NavigationConfig` by
reference, `ViewNavigationConfig` in `@object-ui/types`: `mode` (`page`,
`drawer`, `modal`, `split`, `popover`, `new_window` or `none`) with `size`,
`openNewTab` and `preventNavigation`. `drawer`, `modal` and `popover` open the
entry's record in that overlay, `new_window` and `openNewTab: true` open the
record page in a new tab, `preventNavigation: true` opens nothing, and `size`
sets the overlay width. A click handler from a parent view outranks the whole
key. ⚠️ Write the block with `mode`: with the key absent, or with a block
written without `mode` (it takes the spec's `page` default), a click opens
nothing on a timeline no parent view navigates for (objectui#11293). `split`
opens nothing on this block either, because the timeline gives the split shell
no main panel.

## Links

- 📚 [Documentation](https://www.objectui.org/docs/plugins/plugin-timeline)
Expand Down
44 changes: 42 additions & 2 deletions packages/plugin-timeline/src/ObjectTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import React, { useEffect, useState, useCallback, useMemo, useRef } from 'react';
import type { DataSource, TimelineSchema, ListViewTimelineConfig } from '@object-ui/types';
import type { DataSource, TimelineSchema, ListViewTimelineConfig, ViewNavigationConfig } from '@object-ui/types';
import { useDataScope, useNavigationOverlay, useSafeFieldLabel, useSettledSchema, useDataInvalidation, useFilterScope, useResolvedFilter } from '@object-ui/react';
import { NavigationOverlay } from '@object-ui/components';
import { extractRecords, buildExpandFields, convertSortToQueryParams, createFieldColorResolver, recordDisplayValueAt, toDisplayDate } from '@object-ui/core';
Expand Down Expand Up @@ -171,6 +171,41 @@ export interface ObjectTimelineProps {
* has always declared on it, and that this renderer now actually reads.
*/
timeline?: ListViewTimelineConfig;
/**
* Entry-click navigation — what a click on an entry opens (an overlay
* mode, the record page in a new tab, or nothing), with the overlay `size`
* and the `openNewTab` / `preventNavigation` flags. Handed to
* `useNavigationOverlay` below, which the composed `onItemClick` fires.
*
* DECLARED by objectui#8654, the timeline arm of the objectui#8652
* maintainer ruling, verbatim 「B」: the platform half is
* `@objectstack/spec` 17.5.0, whose `ComponentPropsMap['object-timeline']`
* row declares `navigation: NavigationConfigSchema.optional()` for this
* block standalone, with no enclosing view. So this member follows the
* installed spec rather than leading it. Before it, the read went through
* an `as any` cast, and without the cast it would have compiled only
* through `BaseSchema`'s `[key: string]: any`.
*
* Declared HERE, beside `objectName`, `timeline`, `filter` and `sort`,
* rather than on `TimelineSchema` in `@object-ui/types`, and that is the
* spec's own split: `TimelineSchema` is the presentational rail, whose
* renderer never reads this key, while the spec declares it on the
* object-bound `object-timeline` row only. That row's other members live
* on this half for the same reason. The zod face of the row is
* `ObjectTimelineBlockSchema`'s `properties` bag, which reads the spec row
* by reference and so judges this key already.
*
* Same spec type as `ObjectKanbanSchema.navigation` and
* `ObjectCalendarSchema.navigation` — aligned with `@objectstack/spec`
* `NavigationConfig` rather than restated, so the vocabulary cannot fork.
*
* ⚠️ Unlike the board and the calendar, this renderer supplies NO default
* for an ABSENT key: the hook then resolves the spec's `page`, and a click
* opens nothing on a timeline no parent view navigates for. A parent's
* `onRowClick` / `onItemClick` outranks the whole key. The members are
* pinned in `__tests__/timelineNavigationMembers-8654.test.tsx`.
*/
navigation?: ViewNavigationConfig;
/**
* Query filter for the object fetch, in any shape `toFilterNode` accepts
* (spec `ViewFilterRule[]`, ObjectQL AST nodes, or a MongoDB-style object).
Expand Down Expand Up @@ -750,8 +785,13 @@ export const ObjectTimeline: React.FC<ObjectTimelineProps> = ({
enabled: !!schema.objectName && !!dataSource,
});

// `navigation` is a declared member of this component's schema since
// objectui#8654 (see `ObjectTimelineProps`), so it is read with no cast.
// ⛔ No `?? { mode: 'drawer' }` here: an absent key resolving to the hook's
// `page` is the behaviour the published description states and the member
// pins hold. Changing it is objectui#11293's question, not a read-site edit.
const navigation = useNavigationOverlay({
navigation: (schema as any).navigation,
navigation: schema.navigation,
objectName: schema.objectName,
onRowClick: onRowClick ?? onItemClick,
});
Expand Down
Loading
Loading