@object-ui/components 17.5.0 → 17.7.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 (73) hide show
  1. package/CHANGELOG.md +10155 -0
  2. package/README.md +20 -10
  3. package/dist/custom/button-group.d.ts +2 -2
  4. package/dist/custom/character-count.d.ts +56 -0
  5. package/dist/custom/combobox.d.ts +2 -4
  6. package/dist/custom/config-field-renderer.d.ts +1 -1
  7. package/dist/custom/config-panel-renderer.d.ts +3 -3
  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 +448 -36
  11. package/dist/custom/fullscreen-editor.d.ts +48 -2
  12. package/dist/custom/index.d.ts +27 -28
  13. package/dist/custom/item.d.ts +2 -2
  14. package/dist/custom/kbd.d.ts +1 -1
  15. package/dist/custom/mobile-dialog-content.d.ts +22 -1
  16. package/dist/custom/navigation-overlay.d.ts +105 -0
  17. package/dist/custom/refresh-indicator.d.ts +8 -2
  18. package/dist/custom/resizable.d.ts +1 -1
  19. package/dist/custom/view-states.d.ts +74 -1
  20. package/dist/debug/index.d.ts +2 -2
  21. package/dist/hooks/related-count-store.d.ts +26 -28
  22. package/dist/index.css +174 -364
  23. package/dist/index.d.ts +21 -21
  24. package/dist/index.js +7274 -6350
  25. package/dist/index.umd.cjs +12 -13
  26. package/dist/lib/close-label.d.ts +1 -2
  27. package/dist/lib/date-fns-locale.d.ts +41 -0
  28. package/dist/lib/form-control-dom-props.d.ts +77 -0
  29. package/dist/lib/lazy-icon.d.ts +32 -1
  30. package/dist/lib/lucide-record-icon-names.d.ts +30 -0
  31. package/dist/lib/slider-thumb.d.ts +86 -0
  32. package/dist/lib/utils.d.ts +113 -0
  33. package/dist/notifications/index.d.ts +5 -5
  34. package/dist/renderers/action/action-bar.d.ts +8 -36
  35. package/dist/renderers/action/action-button.d.ts +15 -4
  36. package/dist/renderers/action/action-group.d.ts +5 -4
  37. package/dist/renderers/action/action-icon.d.ts +12 -2
  38. package/dist/renderers/action/action-menu.d.ts +5 -2
  39. package/dist/renderers/action/auto-trigger.d.ts +19 -4
  40. package/dist/renderers/action/resolve-icon.d.ts +27 -2
  41. package/dist/renderers/action/static-params.d.ts +79 -0
  42. package/dist/renderers/action/visibility-gate.d.ts +11 -3
  43. package/dist/renderers/basic/elements.d.ts +6 -3
  44. package/dist/renderers/basic/html-elements.d.ts +8 -4
  45. package/dist/renderers/basic/readProps.d.ts +60 -0
  46. package/dist/renderers/complex/data-table.d.ts +31 -14
  47. package/dist/renderers/complex/dataTableBindDiagnostic.d.ts +187 -0
  48. package/dist/renderers/form/validationRuleKeys.d.ts +45 -0
  49. package/dist/renderers/layout/box.d.ts +8 -0
  50. package/dist/renderers/layout/containers.d.ts +1 -10
  51. package/dist/renderers/layout/react-page.d.ts +16 -2
  52. package/dist/renderers/navigation/sidebar.d.ts +31 -0
  53. package/dist/share/index.d.ts +2 -2
  54. package/dist/ui/badge.d.ts +1 -1
  55. package/dist/ui/button.d.ts +1 -1
  56. package/dist/ui/calendar.d.ts +3 -2
  57. package/dist/ui/carousel.d.ts +2 -2
  58. package/dist/ui/command.d.ts +8 -8
  59. package/dist/ui/form.d.ts +1 -1
  60. package/dist/ui/index.d.ts +46 -47
  61. package/dist/ui/input-otp.d.ts +2 -2
  62. package/dist/ui/pagination.d.ts +1 -1
  63. package/dist/ui/sheet.d.ts +1 -1
  64. package/dist/ui/sidebar.d.ts +4 -3
  65. package/dist/ui/slider.d.ts +2 -1
  66. package/package.json +17 -18
  67. package/dist/SchemaRenderer.d.ts +0 -4
  68. package/dist/custom/action-param-dialog.d.ts +0 -21
  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/renderers/data-display/table.d.ts +0 -2
  72. package/dist/ui/chart.d.ts +0 -56
  73. package/dist/ui/toast.d.ts +0 -22
@@ -5,14 +5,24 @@ import { UIActionSchema } from '@object-ui/types';
5
5
  * `locations`, `enabled` and `size` are all modern-only keys this renderer
6
6
  * forwards, and the legacy `crud.ts` `ActionSchema` is `@deprecated` and pins
7
7
  * `type: 'action'` where this renderer's own registry `inputs` declare
8
- * `'script' | 'url' | 'modal' | 'flow' | 'api'`.
8
+ * `'script' | 'url' | 'modal' | 'flow' | 'api'`. `actionType` is the DECLARED
9
+ * input carrying that execution type (objectui#7415), for the same reason it is
10
+ * on `action:button`; `type` stays on the intersection as the SDUI envelope's
11
+ * component discriminator and is not read as an action type here.
9
12
  */
10
- export interface ActionIconProps {
13
+ export interface ActionIconRendererProps {
11
14
  schema: UIActionSchema & {
12
15
  type: string;
13
16
  className?: string;
17
+ actionType?: string;
14
18
  };
15
19
  className?: string;
16
20
  context?: Record<string, any>;
21
+ /**
22
+ * The host-EVALUATED enablement verdict — see `ActionButtonRendererProps` for the
23
+ * mechanism (objectui#9131). Declared rather than left to the index
24
+ * signature, consumed by name below, never re-spread onto the DOM.
25
+ */
26
+ disabled?: boolean;
17
27
  [key: string]: any;
18
28
  }
@@ -12,8 +12,11 @@ export interface ActionMenuSchema {
12
12
  variant?: string;
13
13
  /** Trigger size */
14
14
  size?: string;
15
- /** Visibility condition */
16
- visible?: string;
15
+ /** Visibility predicate: a boolean, a CEL string, or a `{ dialect, source }` envelope */
16
+ visible?: boolean | string | {
17
+ dialect: string;
18
+ source?: string;
19
+ };
17
20
  /** Custom CSS class */
18
21
  className?: string;
19
22
  [key: string]: any;
@@ -11,10 +11,25 @@
11
11
  * another. Deliberately strict: only the literal `true` arms it.
12
12
  */
13
13
  export declare function hasAutoTrigger(action: unknown): boolean;
14
+ /** The fields of an action this module reads. */
15
+ export interface AutoTriggerSubject {
16
+ autoTrigger?: unknown;
17
+ visible?: unknown;
18
+ name?: string;
19
+ label?: string;
20
+ }
14
21
  /**
15
- * Run `run` at most once, as soon as `armed` is true.
22
+ * Run `run` at most once, as soon as `action` asks to be auto-triggered — unless
23
+ * the action's own declared `visible` gate hides it, in which case the trigger
24
+ * is refused and reported instead (objectui#4191).
16
25
  *
17
- * `run` may change identity freely (it is rebuilt from `schema` on most
18
- * renders); the ref is what makes this once-only, so depending on it is safe.
26
+ * @param action The action the renderer received (read: `autoTrigger`,
27
+ * `visible`, `name`, `label`).
28
+ * @param isVisible The renderer's own evaluated `visible` verdict — the same
29
+ * value its early return consults. Only consulted when the
30
+ * action DECLARES a gate, exactly like that early return.
31
+ * @param run The renderer's click path. It may change identity freely (it
32
+ * is rebuilt from `schema` on most renders); the ref is what
33
+ * makes this once-only, so depending on it is safe.
19
34
  */
20
- export declare function useAutoTriggerOnce(armed: boolean, run: () => void | Promise<void>): void;
35
+ export declare function useAutoTriggerOnce(action: AutoTriggerSubject, isVisible: boolean, run: () => void | Promise<void>): void;
@@ -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,79 @@
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
+ * A container MEMBER with its `properties` evaluated (objectui#10290).
32
+ *
33
+ * `action:bar`, `action:group` and `action:menu` draw their member actions
34
+ * themselves, so a member never passes through the `SchemaRenderer` memo that
35
+ * evaluates a node's `properties`. Its static values ride `properties.params`
36
+ * (objectui#10289), and they are templates (objectui#7867): on a record page,
37
+ * `"recordId": "${record.id}"` has to reach the handler as the record's id. The
38
+ * container evaluates the member's `properties` with the memo's own evaluator
39
+ * and scope (`evaluateBag`, from `useConfigBagEvaluator()`), once, where it
40
+ * composes or runs the member.
41
+ *
42
+ * Returns the member itself when it carries no `properties` bag. Only
43
+ * `properties` is evaluated. It is not copied onto the member (the memo's
44
+ * hoist), and the member's other keys are left as authored.
45
+ */
46
+ export declare function withEvaluatedProperties<T extends object>(member: T, evaluateBag: ConfigBagEvaluator): T;
47
+ /**
48
+ * A container member's static values (objectui#10290): its `properties.params`,
49
+ * evaluated through {@link withEvaluatedProperties}, or `undefined`.
50
+ *
51
+ * For `action:group` / `action:menu` items, which run the member themselves.
52
+ * (`action:bar` hands an inline member to `action:button` / `action:icon` with
53
+ * its `properties` already evaluated, and `readStaticParamValues` reads it
54
+ * there.)
55
+ */
56
+ export declare function readMemberStaticParamValues(member: StaticParamsSubject, evaluateBag: ConfigBagEvaluator): Record<string, unknown> | undefined;
57
+ /**
58
+ * The same ruling on a spec ACTION ENTRY (objectui#10462): `element:button`'s
59
+ * inline `action`, `action:group` / `action:menu` items and `page:header`'s
60
+ * actions. `params` is an entry's `ActionParam[]` input list and nothing else.
61
+ * (An `action:group` / `action:menu` item is also a container member, so its
62
+ * static values are read from `properties.params` and evaluated by the
63
+ * container through {@link readMemberStaticParamValues} (objectui#10290).
64
+ * `UIActionSchema` declares no `properties`, so on a container member this bag
65
+ * is read off the authored object, not off a declared key. `element:button`'s
66
+ * inline `action` and `page:header`'s actions do not read
67
+ * `properties.params`.)
68
+ *
69
+ * One type is still different: `api`. The objectstack#5777 window keeps the
70
+ * runner reading an object `params` as the request payload (with its own
71
+ * development warning, naming `bodyExtra`) until 18, so for `api` the object is
72
+ * returned unchanged. For every other type it is not returned, and a
73
+ * development build says so once per action.
74
+ *
75
+ * `type` is the executor the surface dispatches the entry as. Call this on the
76
+ * non-array branch only; an array `params` is the input list, forwarded as
77
+ * `actionParams`.
78
+ */
79
+ 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
@@ -15,12 +15,15 @@
15
15
  * - element:divider (no-props separator)
16
16
  * - ElementButtonProps -> element:button
17
17
  *
18
- * Heavier interactive elements (element:filter, element:form) live in their
19
- * owning plugins and are left to those packages. element:record_picker — which
20
- * writes its selection into a page variable — ships alongside in `./record-picker`.
18
+ * Heavier interactive elements (element:form) live in their owning plugins and
19
+ * are left to those packages. element:record_picker — which writes its
20
+ * selection into a page variable — ships alongside in `./record-picker`.
21
21
  *
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` also reads the node-level `dataSource` binding: its
26
+ * `object` wins over the flat one, and its filter is AND-combined with the
27
+ * flat one (objectui#10909).
25
28
  */
26
29
  export {};
@@ -7,13 +7,17 @@
7
7
  *
8
8
  * Safe native HTML element passthrough renderers (ADR: kind:'html').
9
9
  *
10
- * A `kind:'html'` page is authored as a constrained JSX/Tailwind string that is
10
+ * A `kind:'html'` page is authored as a constrained JSX string that is
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;
@@ -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 by `BaseSchema`'s `[key: string]: any`, the zod side by `BaseSchema`
36
+ * 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,45 @@
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 validation-rule names react-hook-form actually RUNS (objectui#5099).
10
+ *
11
+ * react-hook-form's field validator destructures a FIXED key set off the
12
+ * field descriptor and silently drops every other key. From the installed
13
+ * 7.85.0 bundle (`dist/index.cjs.js`, minified):
14
+ *
15
+ * const{ref:c,refs:f,required:m,maxLength:y,minLength:p,min:g,max:b,
16
+ * pattern:V,validate:A,name:x,valueAsNumber:S,mount:k}=e._f
17
+ *
18
+ * Of those twelve, seven are authorable validation rules — the rest are
19
+ * field-descriptor internals react-hook-form writes itself and an author
20
+ * never supplies through `FieldValidationRules`. Any rule name outside the
21
+ * seven — a misspelled `minlength`, an invented `email`, the numeric keys
22
+ * left by spreading an ARRAY into `validation` — is dropped without a trace:
23
+ * the field renders, the form looks validated, and the rule never runs. The
24
+ * form renderer reports such names loudly (dev-time `console.error`; see
25
+ * `form.tsx`).
26
+ *
27
+ * Both lists are PINNED against the installed react-hook-form bundle by
28
+ * `__tests__/rhf-recognized-rule-keys.test.ts`, which re-derives the
29
+ * destructured set from `dist/index.cjs.js` at test time. A react-hook-form
30
+ * bump that changes the set turns that test red — forcing a human to re-sort
31
+ * the new key into one of these two lists — instead of silently rotting the
32
+ * diagnostic.
33
+ */
34
+ /**
35
+ * Keys react-hook-form's validator destructures that are NOT authorable
36
+ * rules: descriptor internals the library maintains itself.
37
+ */
38
+ export declare const RHF_DESCRIPTOR_NON_RULE_KEYS: readonly string[];
39
+ /**
40
+ * The authorable rule names react-hook-form's validator reads. Exactly the
41
+ * declared key set of `FieldValidationRules` in `@object-ui/types` —
42
+ * declared = enforced; anything else in a field's `validation` object is a
43
+ * bug in the metadata, not a rule.
44
+ */
45
+ export declare const RHF_RECOGNIZED_RULE_KEYS: ReadonlySet<string>;
@@ -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 {};
@@ -17,13 +17,4 @@
17
17
  * - PageContainerProps -> page:footer / page:sidebar / page:section
18
18
  * (the thin wrappers; one shared `children` slot)
19
19
  */
20
- /**
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.
26
- *
27
- * Exported for unit tests.
28
- */
29
- export declare function cleanupTitleSeparators(s: string): string;
20
+ export {};