@asteby/metacore-runtime-react 18.28.3 → 19.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.
@@ -0,0 +1,99 @@
1
+ // DynamicView — the single entry point a host renders for a model's "list"
2
+ // surface. It picks the renderer from the model's `view_type`:
3
+ // - `'kanban'` → <DynamicKanban>
4
+ // - anything else / absent → <DynamicTable> (the default)
5
+ //
6
+ // The decision is metadata-driven (RFC §1.2): the kernel serves `view_type` +
7
+ // `group_by` on the table metadata, derived from the nav item. A host that
8
+ // already knows the view type can skip this and render the concrete component
9
+ // directly; a generic host route (e.g. ops `/m/$model`) mounts <DynamicView>
10
+ // and lets the metadata decide, so the same model can expose a `table` nav and
11
+ // a `kanban` nav with no host code change.
12
+ //
13
+ // Both child components fetch their own metadata (cache-backed), so the extra
14
+ // read this wrapper does to learn `view_type` is served from the same cache —
15
+ // no duplicate network round-trip in practice.
16
+ import { useEffect, useState } from 'react'
17
+ import { useApi } from './api-context'
18
+ import { useMetadataCache } from './metadata-cache'
19
+ import { DynamicTable, type DynamicTableProps } from './dynamic-table'
20
+ import { DynamicKanban, type DynamicKanbanProps } from './dynamic-kanban'
21
+ import type { TableMetadata, ApiResponse } from './types'
22
+
23
+ /**
24
+ * Pure routing decision: which renderer a `view_type` maps onto. Exported so a
25
+ * host that resolves metadata itself can branch without mounting this wrapper.
26
+ */
27
+ export function resolveViewRenderer(
28
+ viewType: string | undefined,
29
+ ): 'kanban' | 'table' {
30
+ return viewType === 'kanban' ? 'kanban' : 'table'
31
+ }
32
+
33
+ export interface DynamicViewProps extends DynamicTableProps {
34
+ /**
35
+ * Props forwarded to <DynamicKanban> when the model resolves to a kanban
36
+ * view. `model`/`endpoint`/`refreshTrigger`/`timeZone`/`currency` are shared
37
+ * with the table props and forwarded automatically; this is for the
38
+ * kanban-only extras (e.g. `onCardClick`, `pageSize`).
39
+ */
40
+ kanbanProps?: Partial<Omit<DynamicKanbanProps, 'model' | 'endpoint'>>
41
+ }
42
+
43
+ export function DynamicView({ kanbanProps, ...tableProps }: DynamicViewProps) {
44
+ const { model, endpoint, refreshTrigger, timeZone, currency } = tableProps
45
+ const api = useApi()
46
+ const cached = useMetadataCache((s) => s.getMetadata(model))
47
+ const setMeta = useMetadataCache((s) => s.setMetadata)
48
+ const [viewType, setViewType] = useState<string | undefined>(cached?.view_type)
49
+ const [resolved, setResolved] = useState<boolean>(!!cached)
50
+
51
+ useEffect(() => {
52
+ let cancelled = false
53
+ const c = useMetadataCache.getState().getMetadata(model)
54
+ if (c) {
55
+ setViewType(c.view_type)
56
+ setResolved(true)
57
+ }
58
+ api
59
+ .get(`/metadata/table/${model}`)
60
+ .then((res) => {
61
+ if (cancelled) return
62
+ const body = res.data as ApiResponse<TableMetadata>
63
+ const meta = body?.success ? body.data : (res.data as TableMetadata)
64
+ if (meta) {
65
+ setViewType(meta.view_type)
66
+ setMeta(model, meta)
67
+ }
68
+ })
69
+ .catch(() => {
70
+ /* fall back to the table renderer */
71
+ })
72
+ .finally(() => {
73
+ if (!cancelled) setResolved(true)
74
+ })
75
+ return () => {
76
+ cancelled = true
77
+ }
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, [model])
80
+
81
+ // Until we know the view type, render nothing transient-heavy: default to the
82
+ // table renderer only once resolved to avoid a table→kanban flash.
83
+ if (!resolved && !cached) return null
84
+
85
+ if (resolveViewRenderer(viewType) === 'kanban') {
86
+ return (
87
+ <DynamicKanban
88
+ model={model}
89
+ endpoint={endpoint}
90
+ refreshTrigger={refreshTrigger}
91
+ timeZone={timeZone}
92
+ currency={currency}
93
+ {...kanbanProps}
94
+ />
95
+ )
96
+ }
97
+
98
+ return <DynamicTable {...tableProps} />
99
+ }
package/src/index.ts CHANGED
@@ -6,6 +6,21 @@
6
6
  export * from './types'
7
7
  export * from './options-context'
8
8
  export * from './dynamic-table'
9
+ export {
10
+ DynamicKanban,
11
+ type DynamicKanbanProps,
12
+ deriveStages,
13
+ groupByStage,
14
+ isTransitionAllowed,
15
+ applyOptimisticMove,
16
+ selectCardColumns,
17
+ UNASSIGNED_LANE,
18
+ } from './dynamic-kanban'
19
+ export {
20
+ DynamicView,
21
+ resolveViewRenderer,
22
+ type DynamicViewProps,
23
+ } from './dynamic-view'
9
24
  export * from './dynamic-form'
10
25
  export {
11
26
  ActionModalDispatcher,
package/src/types.ts CHANGED
@@ -22,6 +22,54 @@ export interface TableMetadata {
22
22
  * and attachments. Absent on hosts/older kernels — purely additive.
23
23
  */
24
24
  relations?: RelationMeta[]
25
+ /**
26
+ * Which renderer the host should use for this view. `'table'` (default, or
27
+ * absent) → `DynamicTable`; `'kanban'` → `DynamicKanban`. Served by the
28
+ * kernel from the nav item's `view_type` (RFC §1.2). Purely additive — older
29
+ * kernels omit it and the SDK falls back to the table renderer.
30
+ */
31
+ view_type?: 'table' | 'kanban' | (string & {})
32
+ /**
33
+ * Column key the board groups by when `view_type === 'kanban'` (the stage
34
+ * column, e.g. `'stage'`). Each distinct value of this column becomes a board
35
+ * lane. Mirrors the nav item's `group_by` (RFC §1.2).
36
+ */
37
+ group_by?: string
38
+ /**
39
+ * Board lanes (the stage machine of the `group_by`/`stage_field` column).
40
+ * When present the kanban renders one lane per stage in `order`. When absent
41
+ * the SDK derives lanes from the `group_by` column's `options` (the kernel
42
+ * already projects `stages[]` onto the status display — RFC §1.1). Snake_case
43
+ * keys as the kernel serves them.
44
+ */
45
+ stages?: StageMeta[]
46
+ /**
47
+ * Allowed stage transitions (RFC §1.1). When present, the kanban only lets a
48
+ * card drop into a lane reachable from its current stage; disallowed lanes
49
+ * are dimmed and reject the drop. `from`/`to` accept `'*'` as a wildcard.
50
+ * Absent → any move is allowed (the kernel still validates server-side).
51
+ */
52
+ transitions?: StageTransition[]
53
+ }
54
+
55
+ /**
56
+ * One board lane / pipeline stage. Mirrors the kernel v3 `Stage` (RFC §1.1).
57
+ * `color` is a semantic palette name (`'slate'`, `'blue'`, `'amber'`, `'green'`)
58
+ * or a hex literal — resolved through the same `generateBadgeStyles` helper as
59
+ * option badges. `is_final` flags a terminal stage (e.g. "Done").
60
+ */
61
+ export interface StageMeta {
62
+ key: string
63
+ label: string
64
+ color?: string
65
+ order?: number
66
+ is_final?: boolean
67
+ }
68
+
69
+ /** Allowed `from → to` stage transition (RFC §1.1). `'*'` is a wildcard. */
70
+ export interface StageTransition {
71
+ from: string
72
+ to: string
25
73
  }
26
74
 
27
75
  /**