@object-ui/components 17.6.0 → 18.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +9618 -0
  2. package/README.md +27 -3
  3. package/dist/custom/RecordTitleChip.d.ts +4 -3
  4. package/dist/custom/button-group.d.ts +1 -1
  5. package/dist/custom/combobox.d.ts +2 -4
  6. package/dist/custom/dialog-viewport-gutter.d.ts +37 -0
  7. package/dist/custom/empty-state-link.d.ts +63 -0
  8. package/dist/custom/empty.d.ts +16 -4
  9. package/dist/custom/field.d.ts +1 -1
  10. package/dist/custom/filter-builder.d.ts +225 -77
  11. package/dist/custom/grouping-editor.d.ts +4 -0
  12. package/dist/custom/horizontal-overflow.d.ts +18 -0
  13. package/dist/custom/index.d.ts +4 -1
  14. package/dist/custom/item.d.ts +1 -1
  15. package/dist/custom/kbd.d.ts +1 -1
  16. package/dist/custom/mobile-dialog-content.d.ts +22 -1
  17. package/dist/custom/navigation-overlay.d.ts +105 -0
  18. package/dist/custom/refresh-indicator.d.ts +8 -2
  19. package/dist/custom/sort-builder.d.ts +8 -0
  20. package/dist/custom/use-horizontal-overflow.d.ts +36 -0
  21. package/dist/custom/view-states.d.ts +74 -1
  22. package/dist/hooks/related-count-store.d.ts +81 -28
  23. package/dist/index.css +345 -361
  24. package/dist/index.d.ts +11 -3
  25. package/dist/index.js +8480 -6628
  26. package/dist/index.umd.cjs +11 -15
  27. package/dist/lib/close-label.d.ts +1 -2
  28. package/dist/lib/date-fns-locale.d.ts +41 -0
  29. package/dist/lib/inline-markdown.d.ts +64 -0
  30. package/dist/lib/lazy-icon.d.ts +32 -1
  31. package/dist/lib/lucide-record-icon-names.d.ts +30 -0
  32. package/dist/lib/utils.d.ts +113 -0
  33. package/dist/renderers/action/action-bar.d.ts +8 -36
  34. package/dist/renderers/action/action-button.d.ts +15 -4
  35. package/dist/renderers/action/action-group.d.ts +5 -4
  36. package/dist/renderers/action/action-icon.d.ts +12 -2
  37. package/dist/renderers/action/action-menu.d.ts +5 -2
  38. package/dist/renderers/action/auto-trigger.d.ts +19 -4
  39. package/dist/renderers/action/disabled-reason.d.ts +49 -0
  40. package/dist/renderers/action/resolve-icon.d.ts +27 -2
  41. package/dist/renderers/action/static-params.d.ts +71 -0
  42. package/dist/renderers/action/visibility-gate.d.ts +11 -3
  43. package/dist/renderers/basic/data-list.d.ts +2 -1
  44. package/dist/renderers/basic/elements.d.ts +2 -0
  45. package/dist/renderers/basic/html-elements.d.ts +7 -3
  46. package/dist/renderers/basic/readProps.d.ts +60 -0
  47. package/dist/renderers/basic/record-picker.d.ts +3 -2
  48. package/dist/renderers/complex/data-table.d.ts +31 -14
  49. package/dist/renderers/complex/dataTableBindDiagnostic.d.ts +187 -0
  50. package/dist/renderers/layout/box.d.ts +8 -0
  51. package/dist/renderers/layout/containers.d.ts +28 -23
  52. package/dist/renderers/layout/page-block-grid.d.ts +29 -0
  53. package/dist/renderers/layout/page-block-span.d.ts +50 -0
  54. package/dist/renderers/layout/page-description-context.d.ts +14 -0
  55. package/dist/renderers/layout/page-print.d.ts +14 -0
  56. package/dist/renderers/layout/page-region-posture.d.ts +82 -0
  57. package/dist/renderers/layout/react-page.d.ts +4 -0
  58. package/dist/renderers/navigation/sidebar.d.ts +31 -0
  59. package/dist/ui/badge.d.ts +1 -1
  60. package/dist/ui/button.d.ts +1 -1
  61. package/dist/ui/calendar.d.ts +2 -1
  62. package/dist/ui/command.d.ts +7 -7
  63. package/dist/ui/index.d.ts +0 -1
  64. package/dist/ui/input-otp.d.ts +2 -2
  65. package/dist/ui/sheet.d.ts +1 -1
  66. package/dist/ui/sidebar.d.ts +2 -1
  67. package/package.json +14 -15
  68. package/dist/SchemaRenderer.d.ts +0 -4
  69. package/dist/custom/export-progress-dialog.d.ts +0 -43
  70. package/dist/hooks/use-export-job.d.ts +0 -34
  71. package/dist/ui/chart.d.ts +0 -56
  72. package/dist/ui/toast.d.ts +0 -22
@@ -1,6 +1,31 @@
1
1
  import { LucideIcon } from 'lucide-react';
2
2
  /**
3
- * Resolve a Lucide icon by its kebab-case name.
4
- * Returns null if not found.
3
+ * The lookup this seam performs, exposed for DIAGNOSTICS only.
4
+ *
5
+ * `renderers/basic/icon.tsx` names both halves in its objectui#5631 warning
6
+ * (`lookup: "Home" -> "House"`), and that message is under a maintainer ruling
7
+ * 一字不动. Without this it would need its own copy of the tokeniser and the
8
+ * map to keep saying the same thing — which is precisely the duplication this
9
+ * card removed. ⛔ It decides nothing: no call site can change what resolves by
10
+ * reading it.
11
+ */
12
+ export declare function describeIconLookup(name: string): {
13
+ pascal: string;
14
+ key: string;
15
+ };
16
+ /**
17
+ * Resolve an authored Lucide icon name to its component.
18
+ *
19
+ * Accepts kebab-case, snake_case, space-separated and PascalCase spellings.
20
+ * Returns `null` when the name is absent or names no live glyph — deciding what
21
+ * to draw instead belongs to the caller (see the module docblock).
22
+ *
23
+ * ⚠️ The component this returns draws its `<svg>` — with lucide's own classes,
24
+ * box and attributes — SYNCHRONOUSLY, and fills in the path data when the
25
+ * icon's module arrives (objectui#9251). So `svg.lucide-house` matches on the
26
+ * first frame and nothing reflows, while the 1,781 icon modules stay off the
27
+ * eager path. ⛔ A test that asserts on the PATHS inside the svg must await
28
+ * them (`findBy*` / `waitFor`); everything that asks which icon this is —
29
+ * including `null` vs not — is answered in the same tick, as before.
5
30
  */
6
31
  export declare function resolveIcon(name: string | undefined): LucideIcon | null;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /** The two keys this module reads off an action node. */
9
+ export interface StaticParamsSubject {
10
+ params?: unknown;
11
+ properties?: unknown;
12
+ name?: unknown;
13
+ label?: unknown;
14
+ }
15
+ /** Reset the warn-once memo. Exported for tests. */
16
+ export declare function resetStaticParamsWarnings(): void;
17
+ /**
18
+ * The static values to forward as the runner's `ActionDef.params`: the node's
19
+ * `properties.params` when it is an object bag, otherwise `undefined`.
20
+ *
21
+ * A node-level OBJECT `params` that is not the hoisted `properties.params` is
22
+ * never returned; it triggers the development warning above.
23
+ */
24
+ export declare function readStaticParamValues(subject: StaticParamsSubject, where: string): Record<string, unknown> | undefined;
25
+ /**
26
+ * Evaluates a config bag the way `SchemaRenderer` evaluates `properties`: the
27
+ * function `useConfigBagEvaluator()` (`@object-ui/react`) returns.
28
+ */
29
+ export type ConfigBagEvaluator = (bag: unknown) => unknown;
30
+ /**
31
+ * An `action:bar` member with its `properties` evaluated (objectui#10290).
32
+ *
33
+ * `action:bar` mounts its inline members on `action:button` / `action:icon`
34
+ * itself, so a member never passes through the `SchemaRenderer` memo that
35
+ * evaluates a node's `properties`. The renderer it lands on reads the static
36
+ * values off `properties.params` ({@link readStaticParamValues},
37
+ * objectui#10289), and they are templates (objectui#7867): on a record page,
38
+ * `"recordId": "${record.id}"` has to reach the handler as the record's id. The
39
+ * bar evaluates the member's `properties` with the memo's own evaluator and
40
+ * scope (`evaluateBag`, from `useConfigBagEvaluator()`), once, where it
41
+ * composes the member.
42
+ *
43
+ * Returns the member itself when it carries no `properties` bag. Only
44
+ * `properties` is evaluated. It is not copied onto the member (the memo's
45
+ * hoist), and the member's other keys are left as authored.
46
+ *
47
+ * `action:group` and `action:menu` do not call this: their members carry no
48
+ * `properties` bag (objectui#11638).
49
+ */
50
+ export declare function withEvaluatedProperties<T extends object>(member: T, evaluateBag: ConfigBagEvaluator): T;
51
+ /**
52
+ * The same ruling on a spec ACTION ENTRY (objectui#10462): `element:button`'s
53
+ * inline `action`, `action:group` / `action:menu` items and `page:header`'s
54
+ * actions. `params` is an entry's `ActionParam[]` input list and nothing else.
55
+ * (No entry surface reads a `properties.params` bag. `UIActionSchema` declares
56
+ * no `properties`, and the spec refuses one on an `action:group` /
57
+ * `action:menu` member: its static parameter values are not part of the inline
58
+ * action vocabulary, and an action that needs them is its own `action:button`
59
+ * node (objectui#11638).)
60
+ *
61
+ * One type is still different: `api`. The objectstack#5777 window keeps the
62
+ * runner reading an object `params` as the request payload (with its own
63
+ * development warning, naming `bodyExtra`) until 18, so for `api` the object is
64
+ * returned unchanged. For every other type it is not returned, and a
65
+ * development build says so once per action.
66
+ *
67
+ * `type` is the executor the surface dispatches the entry as. Call this on the
68
+ * non-array branch only; an array `params` is the input list, forwarded as
69
+ * `actionParams`.
70
+ */
71
+ export declare function readActionEntryParamValues(entry: StaticParamsSubject, type: unknown, where: string): Record<string, unknown> | undefined;
@@ -34,9 +34,13 @@
34
34
  * scope (objectui#3848).
35
35
  *
36
36
  * The ruling: a gate is declared when normalization still leaves a condition to
37
- * evaluate. `''`, a whitespace-only string, an empty-`source` envelope and any
38
- * non-predicate value are therefore NOT declared, and one definition in core
39
- * says so for the renderers, `SchemaRenderer` and the execution entry alike.
37
+ * evaluate. `''`, a whitespace-only string and an empty-`source` envelope are
38
+ * therefore NOT declared, and one definition in core says so for the
39
+ * renderers, `SchemaRenderer` and the execution entry alike. (A non-predicate
40
+ * value — `0`, `{}`, an array, an `ast`-only envelope — was in that list until
41
+ * objectui#11358, which made it a DECLARED gate that cannot be evaluated: the
42
+ * normalizer keeps it, the evaluator faults on it, and each leg fails it in
43
+ * its key's fault direction — `visible` hidden, `disabled` greyed out.)
40
44
  *
41
45
  * The NAME is kept (`hasDeclaredVisibilityGate`) and this module keeps
42
46
  * re-exporting it: the five member-action call sites — `action:button`,
@@ -68,6 +72,10 @@
68
72
  * | `{ source: '' }` | shown → shown | GREY → on | on → on |
69
73
  * | `' '` (whitespace) | HIDDEN → shown | on → on | GREY → on |
70
74
  * | `0` / `{}` (not a predicate) | shown → shown | GREY → on | on → on |
75
+ *
76
+ * (The table is objectui#3850's measurement. Since objectui#11358 the `0` /
77
+ * `{}` row reads HIDDEN / GREY / on: declared and faulting, each leg's fault
78
+ * direction — pinned in `__tests__/action-empty-predicate-scope.test.tsx`.)
71
79
  * | `true` / `false` / CEL / `${…}`| unchanged | unchanged | unchanged |
72
80
  *
73
81
  * The whitespace row is the one that moves on `visible`, and it moves toward the
@@ -14,6 +14,7 @@
14
14
  * row, no toolbar/card/pagination.
15
15
  *
16
16
  * Props are read off `schema.properties` (spec convention) with a `schema.props`
17
- * fallback, matching the other `element:*` renderers.
17
+ * fallback, matching the other `element:*` renderers. The repeater's query is
18
+ * the node-level `dataSource` binding alone (objectui#11880, objectui#12345).
18
19
  */
19
20
  export {};
@@ -22,5 +22,7 @@
22
22
  * All props are read off `schema.properties` per the spec's
23
23
  * `UIComponent.properties` convention; `schema.props` is also accepted
24
24
  * as a fallback so authors transitioning between conventions keep working.
25
+ * `element:number` reads its query from the node-level `dataSource` binding
26
+ * ONLY: its `object` and its `filter` (objectui#10909, objectui#11880).
25
27
  */
26
28
  export {};
@@ -11,9 +11,13 @@
11
11
  * PARSED (never executed) into the SDUI tree. For that tier to live up to its
12
12
  * name, the everyday HTML tags an author reaches for — headings, paragraphs,
13
13
  * lists, links, images, emphasis — must each resolve to a renderer (otherwise
14
- * the parser flags them `unknown-component`). div / span / table / code / label
15
- * and the semantic sectioning tags are registered elsewhere; this module fills
16
- * in the rest of the safe flow/inline set.
14
+ * the parser flags them `unknown-component`). div / span / table / label and
15
+ * the semantic sectioning tags are registered elsewhere; this module fills in
16
+ * the rest of the safe flow/inline set — `code` included (objectui#10756): the
17
+ * bare `code` key used to be the `field:code` widget's namespace fallback, so
18
+ * `<code>inline</code>` on an html page drew a code EDITOR and dropped its
19
+ * text. The widget now stands down from the bare key (`FIELD_TYPES_SKIP_FALLBACK`
20
+ * in `@object-ui/fields`) and stays reachable as `field:code`.
17
21
  *
18
22
  * Safety: these render real DOM, but the html tier never executes JS, so props
19
23
  * are static literals from the parser. As defense-in-depth the passthrough
@@ -0,0 +1,60 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * The config bag an `element:*` renderer reads — THE one definition of it in
10
+ * `@object-ui/components` (objectui#6783).
11
+ *
12
+ * Per spec, element components carry their config in `schema.properties`;
13
+ * `schema.props` is tolerated as the legacy alias so JSON written either way
14
+ * works. `properties` wins a contested key — the 2026-08-18 cross-channel
15
+ * ruling (objectui#5123), whose spread order is preserved here verbatim and
16
+ * pinned by `packages/components/src/__tests__/alias-precedence-cross-channel.test.tsx`.
17
+ *
18
+ * ## The third channel (objectui#6783)
19
+ *
20
+ * This body used to be copied into five renderer modules — `elements.tsx`,
21
+ * `data-list.tsx`, `text-input.tsx`, `record-picker.tsx`,
22
+ * `metadata-viewer.tsx` — each spelling the two halves as
23
+ * `(schema?.properties ?? {}) as T`. `??` only replaces `null`/`undefined`, so
24
+ * a DEGENERATE bag (a string, a number, an array) went straight into the
25
+ * object spread and was re-read as its own indexed keys: for
26
+ * `properties: 'not-a-bag'` the bag a renderer received was
27
+ * `{ '0': 'n', '1': 'o', … '8': 'g' }` — nine keys nobody authored.
28
+ *
29
+ * That is the same hazard objectui#6752 and objectui#6760 closed in
30
+ * `packages/react/src/SchemaRenderer.tsx` on the two channels UPSTREAM of this
31
+ * one (the `props` evaluation memo plus `propsWithoutCanonicalKeys`, and the
32
+ * `properties` hoist). Those guards do not reach here, and are not meant to:
33
+ * what they buy is that the AUTHORED value's shape survives into the node, so
34
+ * a renderer downstream still receives `schema.properties === 'not-a-bag'` and
35
+ * gets to answer the question for itself. The three channels are in SERIES.
36
+ *
37
+ * ## Why it asks `isConfigBag` rather than carrying its own predicate
38
+ *
39
+ * Because a copy that DRIFTS produces no error — each spelling is a boolean
40
+ * expression, so two channels answering "is this a real config bag?"
41
+ * differently is silent by construction. objectui#6761 ended that inside
42
+ * `@object-ui/react` by converging six occurrences behind one exported
43
+ * definition and pinning it (`utils/configBag.pin.test.ts` fails on a seventh
44
+ * spelling); `@object-ui/components` depends on `@object-ui/react` — every one
45
+ * of the five modules above already imports from it — so the reachable answer
46
+ * here is to READ that definition, not to write the seventh spelling one
47
+ * package over where its pin cannot see it.
48
+ *
49
+ * ## What the guard buys, measured
50
+ *
51
+ * Not a rendered pixel, on today's tree: all five renderers read NAMED keys
52
+ * off this bag, and the one onward spread (`metadata-viewer`'s
53
+ * `<StateMachineView {...props} />`) hands them to components that destructure
54
+ * named fields, so the indexed keys were computed and then discarded. What it
55
+ * buys is the same thing objectui#6752 measured its own guard buys — the
56
+ * authored value's shape is not reinterpreted — one channel further down,
57
+ * which is exactly the property that stops mattering only until the first of
58
+ * these five spreads its bag onto a DOM element.
59
+ */
60
+ export declare function readProps<T extends Record<string, any>>(schema: any): T;
@@ -10,8 +10,9 @@
10
10
  *
11
11
  * Data binding follows the spec's ElementDataSource (`schema.dataSource`):
12
12
  * { object, view?, filter?, sort?, limit? }
13
- * with `properties.object` accepted as a fallback. Display config is read off
14
- * `schema.properties`:
13
+ * and is read from there ONLY: the flat `properties.object` / `filter` /
14
+ * `sort` / `limit` are not read (objectui#11880, objectstack#11509 ruled
15
+ * A-narrow). Display config is read off `schema.properties`:
15
16
  * { labelField='name', valueField='id', label?, placeholder?, emptyText? }
16
17
  *
17
18
  * The selection is written through `usePageVariableBinding(schema.id)`: the
@@ -40,26 +40,43 @@ type BuiltinRowPredicates = NonNullable<DataTableSchema['rowEditPredicates'] | D
40
40
  export declare function isBuiltinRowActionVisible(predicates: Pick<BuiltinRowPredicates, 'visibleWhen'> | undefined, name: 'edit' | 'delete', row: any, scope: Record<string, unknown>): boolean;
41
41
  /**
42
42
  * Does this schema-driven custom row action render for THIS row? Same
43
- * single-definition rule as the built-ins above.
43
+ * single-definition rule as the built-ins above, and the ONE definition for
44
+ * every row menu: this file's (the related list's data table) and
45
+ * plugin-grid's `RowActionMenu` (its "⋮" items, its inline primary buttons and
46
+ * its "⋮" guard), which imports it from the `@object-ui/components` barrel
47
+ * rather than keeping a twin (objectui#11294).
44
48
  *
45
- * A gate counts as DECLARED by `!= null && !== ''`, never by truthiness
46
- * (objectui#3758). Truthiness cannot answer the question: `visible: false` is a
47
- * declared gate that excludes every row, and testing `!action.visible`
48
- * classified it as *ungated* — so the most explicit way to say "never show this"
49
- * rendered the item for everyone, and counted toward the "⋮" guard. This is the
50
- * invariant objectui#3492 established for the selection bar (plugin-grid's
51
- * `hasVisibilityGate`), and the same `!= null` posture the built-in `visibleWhen`
52
- * gate above has always had. The boolean then decides in
53
- * {@link evalRowActionVisibility}, which short-circuits it instead of handing it
54
- * to the engine.
49
+ * Whether a gate is DECLARED at all is asked with `hasDeclaredVisibilityGate`,
50
+ * the action family's one definition (objectui#3812): the related list's
51
+ * toolbar, `action:*`, `DeclaredActionsBar` and the rest ask it too, so an
52
+ * action cannot show in a list's header and be missing from its rows.
55
53
  *
56
- * `''` is grouped with `null` deliberately: an empty predicate is nothing to
57
- * evaluate, so it must not hide the item from everyone either.
54
+ * - `visible: false` is a declared gate that excludes every row. Truthiness
55
+ * cannot answer the question: testing `!action.visible` read it as ungated
56
+ * and rendered the item for everyone (objectui#3758). The boolean then
57
+ * decides in {@link evalRowActionVisibility}, which short-circuits it instead
58
+ * of handing it to the engine.
59
+ * - A blank predicate is no gate, so the action shows: `''`, a whitespace-only
60
+ * string, and an envelope whose `source` is blank. The blank is still
61
+ * reported (ADR-0137 D4) through the family's one `[blank]` report, once per
62
+ * blank spelling rather than once per row. This test used to be
63
+ * `pred == null || pred === ''`, so a whitespace-only `visible` counted as
64
+ * declared, was evaluated, and failed closed: the action showed in a related
65
+ * list's toolbar and was missing from its rows (objectui#11294).
66
+ * - A value that is not a predicate at all (`0`, `{}`, an array, an
67
+ * `ast`-only envelope) IS a declared gate, one that cannot be evaluated
68
+ * (objectui#11358): the family's definition answers "declared", the row fold
69
+ * faults on it and fails closed, so the action is hidden and the fault is
70
+ * reported — as on every other member of the family.
71
+ *
72
+ * `fields` is the object's field definitions, for a caller that has them:
73
+ * plugin-grid passes them so a relation compares as its stored foreign key
74
+ * (see `evalRowPredicate`). This file's own callers pass none.
58
75
  */
59
76
  export declare function isCustomRowActionVisible(action: {
60
77
  name?: string;
61
78
  visible?: unknown;
62
- } | undefined, row: any, scope: Record<string, unknown>): boolean;
79
+ } | undefined, row: any, scope: Record<string, unknown>, fields?: unknown): boolean;
63
80
  /** What the row overflow menu will actually render for ONE row. */
64
81
  export interface DataTableRowMenuPlan {
65
82
  /** The built-in Edit item renders for this row. */
@@ -0,0 +1,187 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * The diagnostics that say out loud what `data-table` has always done silently
10
+ * with a key the AUTHOR wrote.
11
+ *
12
+ * TWO questions, one channel. The file is named for the first because it came
13
+ * first (objectui#6575); it was never limited to it.
14
+ *
15
+ * 1. `bind` was authored — and `data-table` does not read `bind` at all
16
+ * (objectui#6575, maintainer ruling 2026-08-27, option A: 「同意」).
17
+ * 2. `data` was authored and is NOT an array — so the renderer dropped it to
18
+ * zero rows at `data-table.tsx`'s
19
+ * `Array.isArray(rawData) ? rawData : EMPTY_ROWS` (objectui#6665).
20
+ *
21
+ * They are deliberately two predicates asking two questions, not one widened
22
+ * predicate. The nodes that trip #2 carry no `bind` AT ALL, so #6575 staying
23
+ * silent on them is correct behaviour rather than a gap — the triage ruling on
24
+ * objectui#6665 is explicit about that, and widening #6575's question would
25
+ * have made its silence look like the defect. See {@link describeNonArrayData}.
26
+ *
27
+ * ## The first defect this names
28
+ *
29
+ * `bind` is the data-scope binding vocabulary: a path string resolved by
30
+ * `useDataScope()`. `list`, `tree-view` and the `object-*` plugin widgets read
31
+ * it. `DataTableRenderer` does NOT — it takes its rows from `data: rawData =
32
+ * EMPTY_ROWS` off the node, and the file calls no such hook.
33
+ *
34
+ * A `bind` on a `data-table` is nevertheless accepted by every gate: the TS
35
+ * side because `BaseSchema` declares `bind` for every node (objectui#6357), the
36
+ * zod side by `BaseSchema` being `.passthrough()` (which `DataTableSchema.extend(…)` inherits). So the
37
+ * author gets a table drawing a correct-looking header over the "No results
38
+ * found" empty state, with no error, no warning and no diagnostic — the
39
+ * hardest failure shape for a human OR an AI author to self-check, because a
40
+ * rendered header reads as a success receipt.
41
+ *
42
+ * The platform was already paying a teaching cost for it rather than a
43
+ * diagnostic cost: `skills/objectui/rules/protocol.md` documents the pothole
44
+ * verbatim, and `skill-guide-data-table-binding.test.tsx` pins the behaviour.
45
+ * This module moves the warning from the docs to the console, where the author
46
+ * who did not read the docs is standing.
47
+ *
48
+ * ## What it deliberately does NOT do
49
+ *
50
+ * It changes NO behaviour. `data-table` still does not read `bind`, and the
51
+ * ruling is explicit that it must not start: making it a `useDataScope` reader
52
+ * (option B) is a separate published-surface question needing its own ruling.
53
+ * Refusing the key at parse (option C) stays blocked on the `.passthrough()`
54
+ * ceiling (objectui#5155 / objectui#6269). So the trap stops being silent; it
55
+ * does not stop being a trap.
56
+ *
57
+ * ⭐ The `data`-vs-`bind` PRECEDENCE half of that open question DISSOLVED at
58
+ * objectui#9308 rather than being answered. While `useDataScope` walked the
59
+ * injected adapter, `bind` and the node's own `data` were two spellings of one
60
+ * idea — "where this table's rows come from" — and a reader of both would have
61
+ * needed a rule. Since that ruling `bind` resolves a path in the ambient
62
+ * predicate scope and `data` is an inline array on the node: different
63
+ * channels, different questions, no precedence to decide. objectui#6575's
64
+ * ruling that `data-table` must not read `bind` is untouched by that, and this
65
+ * diagnostic's job is unchanged.
66
+ *
67
+ * ## Why a console warning, and only a console warning
68
+ *
69
+ * The same channel `plugin-grid`'s `columnSpellingDiagnostics.ts` uses for the
70
+ * identical shape of failure ("you declared something and the renderer dropped
71
+ * it"): a pure `describe…` function returning `string | null`, called from a
72
+ * `useEffect` keyed on the schema slice, one `console.warn`, no NODE_ENV
73
+ * branch. Deliberately the SAME shape rather than a second, differently-shaped
74
+ * one next to it. Rendering an in-table message instead would be user-facing
75
+ * copy needing all ten locale packs in `@object-ui/i18n`; a throw would take
76
+ * the surrounding page down for a defect that costs one table.
77
+ *
78
+ * The rate limit is the `useEffect` key, and that is enough HERE for a reason
79
+ * `visibilityDiagnostic.ts` does not have available: a node gate is evaluated
80
+ * once per row, so it needs a module-level dedupe `Set` to keep one authoring
81
+ * bug from printing N lines. A `data-table` is one node rendered once — the
82
+ * effect key is one line per mount per distinct `bind`, which is already the
83
+ * "one line per distinct authoring bug" ceiling that `Set` exists to buy.
84
+ *
85
+ * ## The message never asserts something it did not check
86
+ *
87
+ * A node can carry BOTH an inline `data` array and a `bind`. That table is not
88
+ * empty, so the "header over an empty body" consequence would be false there —
89
+ * and a diagnostic that overstates its own consequence teaches authors to
90
+ * distrust it. The two cases get two different consequence clauses, decided by
91
+ * looking at the rows the renderer actually resolved.
92
+ */
93
+ /** Prefix for every `bind` line — the handle tests and greps hold. */
94
+ export declare const DATA_TABLE_BIND_DIAGNOSTIC_PREFIX = "[ObjectUI] DataTable bind:";
95
+ /** The key `data-table` really reads its rows from. */
96
+ export declare const DECLARED_ROWS_KEY = "data";
97
+ /** Where the offending node lives, for the first line of either message. */
98
+ export interface DataTableNodeAddress {
99
+ /** The schema node's `type` — `data-table`, or an alias that routes here. */
100
+ blockType?: unknown;
101
+ /** The node's `id`, when it has one. */
102
+ id?: unknown;
103
+ /** The table's authored caption — often the only human-readable name. */
104
+ caption?: unknown;
105
+ }
106
+ /**
107
+ * Was a `bind` authored on this node?
108
+ *
109
+ * `undefined` is absence — a destructuring default or an omitted key. Every
110
+ * other value, including `null` and the empty string, is something the author
111
+ * WROTE, and writing it bought nothing. Exported so the renderer's effect key
112
+ * and this judgement cannot drift apart: one predicate, two readers.
113
+ */
114
+ export declare function hasAuthoredBind(bind: unknown): boolean;
115
+ /**
116
+ * The message for a `data-table` node carrying a `bind`, or `null` when there
117
+ * is nothing to say.
118
+ *
119
+ * `rows` is what the renderer resolved for the body — passed in rather than
120
+ * re-derived, so the consequence sentence is measured against the same array
121
+ * the reader is looking at.
122
+ *
123
+ * Naming the ADDRESS is the point: which node, which path it spells, what the
124
+ * renderer did instead, and what to write to get the rows on screen. A message
125
+ * that only said something went wrong would leave the author where the silence
126
+ * did.
127
+ */
128
+ export declare function describeIgnoredBind(bind: unknown, rows: unknown, address: DataTableNodeAddress): string | null;
129
+ /** Prefix for every non-array `data` line — the handle tests and greps hold. */
130
+ export declare const DATA_TABLE_DATA_DIAGNOSTIC_PREFIX = "[ObjectUI] DataTable data:";
131
+ /**
132
+ * Was `data` authored as something that is not an array?
133
+ *
134
+ * `undefined` is absence — an omitted key, or the renderer's destructuring
135
+ * default. Everything else the author WROTE, and `data-table.tsx` reduces every
136
+ * non-array value of it to `EMPTY_ROWS` without a word.
137
+ *
138
+ * ## Why this is not {@link hasAuthoredBind}'s question
139
+ *
140
+ * That one asks "was a `bind` authored?" — a key `data-table` does not read at
141
+ * all. This asks about `data`, the key it genuinely does read, and asks a
142
+ * SHAPE question about it rather than a presence one. The nodes that trip this
143
+ * carry no `bind`, so #6575's predicate is correctly silent on them; that
144
+ * silence is precisely why a second question had to be asked here instead of
145
+ * the first one being widened.
146
+ *
147
+ * ## Why the predicate is not keyed on the `${...}` shape
148
+ *
149
+ * The expression string is the spelling that was REPORTED, but it is not the
150
+ * defect. `Array.isArray(rawData) ? rawData : EMPTY_ROWS` swallows a number, an
151
+ * object, a `null` and a plain string exactly as silently, so a predicate that
152
+ * only caught `${...}` would leave the next non-array spelling to arrive as a
153
+ * fresh card. The shape only chooses the wording (see {@link TEMPLATE_SHAPE}).
154
+ *
155
+ * Exported so the renderer and this judgement cannot drift apart: one
156
+ * predicate, two readers.
157
+ */
158
+ export declare function hasNonArrayAuthoredData(rawData: unknown): boolean;
159
+ /**
160
+ * The message for a `data-table` node whose `data` is not an array, or `null`
161
+ * when there is nothing to say.
162
+ *
163
+ * ## Why no measured-consequence clause, unlike {@link describeIgnoredBind}
164
+ *
165
+ * That one has to look at the rows the renderer resolved, because a node can
166
+ * carry BOTH a `bind` and a real inline `data` array — "header over an empty
167
+ * body" would be false there. Here it cannot be: `data` is the ONLY row source
168
+ * `data-table` has, and this function is called exactly when that source is not
169
+ * an array, so the body is `EMPTY_ROWS` by construction. The consequence is
170
+ * stated because the predicate already established it, not asserted on faith.
171
+ *
172
+ * ## What it deliberately does NOT say
173
+ *
174
+ * It does not tell the author to move the expression under `properties`. The
175
+ * same expression IS evaluated there — that contrast is what makes this a
176
+ * defect rather than a design — but whether `properties` is an authoring
177
+ * channel for the `ui:*` / `page:*` namespaces is an open contract question,
178
+ * recorded in `skills/objectui/rules/protocol.md` rather than recommended by
179
+ * it. A console line is the wrong place to settle that, so the message teaches
180
+ * the one route the guides do teach: the host resolves the rows.
181
+ *
182
+ * It also changes NO behaviour. Making node-level `data` evaluate expressions
183
+ * is a behaviour change on a published component; the objectui#6665 triage
184
+ * ruling put that arm on the maintainer floor and dispatched only this one.
185
+ * The trap stops being silent; it does not stop being a trap.
186
+ */
187
+ export declare function describeNonArrayData(rawData: unknown, address: DataTableNodeAddress): string | null;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ export {};
@@ -1,29 +1,34 @@
1
+ import { default as React } from 'react';
1
2
  /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
3
+ * The props of {@link PageHeaderActions}.
4
4
  *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- *
8
- * Spec-aligned container renderers for the `page:*` component namespace.
9
- * Backs the Page-as-root record detail page model (Salesforce Lightning
10
- * Record Page parity).
11
- *
12
- * Maps `packages/spec/src/ui/component.zod.ts` props:
13
- * - PageTabsProps -> page:tabs
14
- * - PageCardProps -> page:card
15
- * - PageAccordionProps -> page:accordion
16
- * - PageHeaderProps -> page:header
17
- * - PageContainerProps -> page:footer / page:sidebar / page:section
18
- * (the thin wrappers; one shared `children` slot)
5
+ * `actions`, `maxVisible` and `mobileMaxVisible` are `page:header`'s own
6
+ * authored keys, read as that block reads them; the system actions are not a
7
+ * prop, they come from `RecordContext.headerSystemActions` as they always did.
19
8
  */
9
+ export interface PageHeaderActionsProps {
10
+ /** The authored `actions` array: action ids (the contract) or, during the transition, inline action objects. */
11
+ actions?: unknown;
12
+ /** The inline-button budget on desktop, as `page:header`'s `maxVisible`; an out-of-contract value falls back to 3. */
13
+ maxVisible?: unknown;
14
+ /** The inline-button budget on mobile, as `page:header`'s `mobileMaxVisible`; an out-of-contract value falls back to 1. */
15
+ mobileMaxVisible?: unknown;
16
+ /** Drawn in place of the action row when no action survives the gates. */
17
+ emptySlot?: React.ReactNode;
18
+ }
20
19
  /**
21
- * Strip dangling connectors that survive when a `titleFormat` interpolates
22
- * with one side empty — e.g. `{number} - {name}` becomes `CTR-0001 -` when
23
- * `name` is blank. Removes a trailing/leading hyphen / middle-dot / colon /
24
- * slash / pipe (optionally surrounded by whitespace) and collapses
25
- * adjacent whitespace into a single space. Idempotent.
20
+ * A record's header actions: the declared actions placed at `record_header` /
21
+ * `record_more` and the host's system actions (Edit / Share / Delete), gated,
22
+ * ordered, split into inline buttons and the `⋯` overflow, and dispatched
23
+ * through the action runner.
26
24
  *
27
- * Exported for unit tests.
25
+ * This is `page:header`'s action row, lifted out of `PageHeaderRenderer` so
26
+ * that a host can draw the same row where a page has no `page:header`
27
+ * (objectui#12906): a page that declares `print` may hold only the printable
28
+ * component types, and `page:header` is not one of them, so the console's
29
+ * record view draws this row in its screen-only chrome beside Print. It is
30
+ * the ONE resolver for both surfaces, ⛔ never a second one. It must render
31
+ * inside the record's `RecordContext` and `ActionProvider`, as `page:header`
32
+ * does.
28
33
  */
29
- export declare function cleanupTitleSeparators(s: string): string;
34
+ export declare const PageHeaderActions: React.FC<PageHeaderActionsProps>;
@@ -0,0 +1,29 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * Lays a list of page blocks on the 12-column block grid (objectui#12624):
4
+ * each block takes the span its kind gets from `./page-block-span`, folded by
5
+ * breakpoint. One component for every block list that folds this way: a
6
+ * region's blocks (`RegionContent` in `./page.tsx`) and the children of
7
+ * `page:card` / `page:section` (`./containers.tsx`), so a container in a
8
+ * region spans 12 by its own kind and its children fold like region blocks.
9
+ * A cell is `empty:hidden`, so a block that renders nothing leaves no gap.
10
+ */
11
+ export declare const PageBlockGrid: React.FC<{
12
+ nodes: readonly unknown[];
13
+ className?: string;
14
+ /** Marks the list as a rail's: every block in it, nested ones too, spans 12. */
15
+ rail?: boolean;
16
+ /** Key prefix for blocks with no `id`. */
17
+ keyPrefix?: string;
18
+ /** Maps a node to the schema `SchemaRenderer` renders (identity by default). */
19
+ toSchema?: (node: unknown) => unknown;
20
+ attributes?: Record<string, string | undefined>;
21
+ /** The grid element, for a caller that measures it (the region's posture, objectui#12703). */
22
+ gridRef?: React.Ref<HTMLDivElement>;
23
+ }>;
24
+ /**
25
+ * A container's child slot as a block grid: a list goes on the grid, while a
26
+ * bare node or text renders as it did (`renderChildren`), since there is
27
+ * nothing to lay out beside it.
28
+ */
29
+ export declare function renderBlockChildren(children: unknown, render: (c: unknown) => React.ReactNode): React.ReactNode;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * How wide a page block sits in its region's 12-column block grid
10
+ * (objectui#12624).
11
+ *
12
+ * The width is DERIVED from the block's kind, never authored:
13
+ * `@objectstack/spec`'s `PageComponentSchema` declares no `span`, and on
14
+ * `PageRegionSchema` `span` is only a refused alias of `width`. So this file
15
+ * adds no key an author can write; it names, once, which registered block
16
+ * types are metric tiles (3 of 12) and which are charts (6 of 12). Every other
17
+ * type, a type this table does not name included, spans the full 12.
18
+ *
19
+ * The keys are the registry keys the blocks are registered under (the bare
20
+ * key where the registration keeps one, and the namespaced key). The page
21
+ * renderer (`RegionContent` in `./page.tsx`) and the Studio page designer's
22
+ * block card (app-shell `PageBlockCanvas`) both read this table, so the span
23
+ * the designer shows is the span the page draws.
24
+ */
25
+ export type PageBlockSpan = 3 | 6 | 12;
26
+ /** Registered block type → its span, for the types narrower than 12. */
27
+ export declare const PAGE_BLOCK_SPANS: ReadonlyMap<string, 3 | 6>;
28
+ /**
29
+ * The span a block of this type takes; a type the table does not name takes
30
+ * 12. A block in a `rail` region (below) takes 12 whatever its kind: a rail is
31
+ * a narrow column, where a fold by viewport width would squeeze a tile to a
32
+ * sliver.
33
+ */
34
+ export declare function pageBlockSpan(type: unknown, rail?: boolean): PageBlockSpan;
35
+ /**
36
+ * Is this region a rail — the narrow `sidebar` / `aside` column beside `main`?
37
+ * Both are on every layout that places those slots beside `main` (no
38
+ * template, `default`, `full-width`, `three-column`); `header-sidebar-main`
39
+ * places only `sidebar` there (an `aside` joins `main`'s column); the
40
+ * `dashboard` template makes every region but `header` / `footer` a grid cell,
41
+ * so it has none. The page renderer passes the same answer structurally, by
42
+ * the slot it places a region in.
43
+ */
44
+ export declare function isPageRailRegion(regionName: unknown, template?: unknown): boolean;
45
+ /**
46
+ * The span folded by breakpoint: as declared from `xl` (1280px) up; between
47
+ * `md` (768px) and `xl` tiles take 6 and charts 12; under `md` every block
48
+ * takes 12. LITERAL class strings so Tailwind's scanner emits them.
49
+ */
50
+ export declare const PAGE_BLOCK_SPAN_CLASS: Readonly<Record<PageBlockSpan, string>>;