@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.
- package/CHANGELOG.md +9618 -0
- package/README.md +27 -3
- package/dist/custom/RecordTitleChip.d.ts +4 -3
- package/dist/custom/button-group.d.ts +1 -1
- package/dist/custom/combobox.d.ts +2 -4
- package/dist/custom/dialog-viewport-gutter.d.ts +37 -0
- package/dist/custom/empty-state-link.d.ts +63 -0
- package/dist/custom/empty.d.ts +16 -4
- package/dist/custom/field.d.ts +1 -1
- package/dist/custom/filter-builder.d.ts +225 -77
- package/dist/custom/grouping-editor.d.ts +4 -0
- package/dist/custom/horizontal-overflow.d.ts +18 -0
- package/dist/custom/index.d.ts +4 -1
- package/dist/custom/item.d.ts +1 -1
- package/dist/custom/kbd.d.ts +1 -1
- package/dist/custom/mobile-dialog-content.d.ts +22 -1
- package/dist/custom/navigation-overlay.d.ts +105 -0
- package/dist/custom/refresh-indicator.d.ts +8 -2
- package/dist/custom/sort-builder.d.ts +8 -0
- package/dist/custom/use-horizontal-overflow.d.ts +36 -0
- package/dist/custom/view-states.d.ts +74 -1
- package/dist/hooks/related-count-store.d.ts +81 -28
- package/dist/index.css +345 -361
- package/dist/index.d.ts +11 -3
- package/dist/index.js +8480 -6628
- package/dist/index.umd.cjs +11 -15
- package/dist/lib/close-label.d.ts +1 -2
- package/dist/lib/date-fns-locale.d.ts +41 -0
- package/dist/lib/inline-markdown.d.ts +64 -0
- package/dist/lib/lazy-icon.d.ts +32 -1
- package/dist/lib/lucide-record-icon-names.d.ts +30 -0
- package/dist/lib/utils.d.ts +113 -0
- package/dist/renderers/action/action-bar.d.ts +8 -36
- package/dist/renderers/action/action-button.d.ts +15 -4
- package/dist/renderers/action/action-group.d.ts +5 -4
- package/dist/renderers/action/action-icon.d.ts +12 -2
- package/dist/renderers/action/action-menu.d.ts +5 -2
- package/dist/renderers/action/auto-trigger.d.ts +19 -4
- package/dist/renderers/action/disabled-reason.d.ts +49 -0
- package/dist/renderers/action/resolve-icon.d.ts +27 -2
- package/dist/renderers/action/static-params.d.ts +71 -0
- package/dist/renderers/action/visibility-gate.d.ts +11 -3
- package/dist/renderers/basic/data-list.d.ts +2 -1
- package/dist/renderers/basic/elements.d.ts +2 -0
- package/dist/renderers/basic/html-elements.d.ts +7 -3
- package/dist/renderers/basic/readProps.d.ts +60 -0
- package/dist/renderers/basic/record-picker.d.ts +3 -2
- package/dist/renderers/complex/data-table.d.ts +31 -14
- package/dist/renderers/complex/dataTableBindDiagnostic.d.ts +187 -0
- package/dist/renderers/layout/box.d.ts +8 -0
- package/dist/renderers/layout/containers.d.ts +28 -23
- package/dist/renderers/layout/page-block-grid.d.ts +29 -0
- package/dist/renderers/layout/page-block-span.d.ts +50 -0
- package/dist/renderers/layout/page-description-context.d.ts +14 -0
- package/dist/renderers/layout/page-print.d.ts +14 -0
- package/dist/renderers/layout/page-region-posture.d.ts +82 -0
- package/dist/renderers/layout/react-page.d.ts +4 -0
- package/dist/renderers/navigation/sidebar.d.ts +31 -0
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/button.d.ts +1 -1
- package/dist/ui/calendar.d.ts +2 -1
- package/dist/ui/command.d.ts +7 -7
- package/dist/ui/index.d.ts +0 -1
- package/dist/ui/input-otp.d.ts +2 -2
- package/dist/ui/sheet.d.ts +1 -1
- package/dist/ui/sidebar.d.ts +2 -1
- package/package.json +14 -15
- package/dist/SchemaRenderer.d.ts +0 -4
- package/dist/custom/export-progress-dialog.d.ts +0 -43
- package/dist/hooks/use-export-job.d.ts +0 -34
- package/dist/ui/chart.d.ts +0 -56
- package/dist/ui/toast.d.ts +0 -22
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { LucideIcon } from 'lucide-react';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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 /
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
*
|
|
14
|
-
* `
|
|
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
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
-
* `
|
|
57
|
-
*
|
|
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
|
|
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;
|
|
@@ -1,29 +1,34 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
3
|
+
* The props of {@link PageHeaderActions}.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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>>;
|