@object-ui/components 17.3.0 → 17.6.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 +2732 -1
- package/README.md +38 -38
- package/dist/custom/button-group.d.ts +1 -1
- package/dist/custom/character-count.d.ts +56 -0
- package/dist/custom/config-field-renderer.d.ts +1 -1
- package/dist/custom/config-panel-renderer.d.ts +3 -3
- package/dist/custom/export-progress-dialog.d.ts +1 -1
- package/dist/custom/filter-builder.d.ts +284 -4
- package/dist/custom/fullscreen-editor.d.ts +102 -0
- package/dist/custom/index.d.ts +28 -27
- package/dist/custom/item.d.ts +1 -1
- package/dist/custom/resizable.d.ts +1 -1
- package/dist/debug/index.d.ts +2 -2
- package/dist/index.css +36 -6
- package/dist/index.d.ts +21 -20
- package/dist/index.js +4991 -4464
- package/dist/index.umd.cjs +10 -6
- package/dist/lib/form-control-dom-props.d.ts +77 -0
- package/dist/lib/sidebar-cookie.d.ts +55 -0
- package/dist/lib/slider-thumb.d.ts +86 -0
- package/dist/notifications/index.d.ts +5 -5
- package/dist/renderers/action/action-bar.d.ts +3 -3
- package/dist/renderers/action/action-button.d.ts +12 -2
- package/dist/renderers/action/action-group.d.ts +10 -4
- package/dist/renderers/action/action-icon.d.ts +10 -2
- package/dist/renderers/action/action-menu.d.ts +20 -2
- package/dist/renderers/action/auto-trigger.d.ts +20 -0
- package/dist/renderers/action/visibility-gate.d.ts +81 -0
- package/dist/renderers/basic/elements.d.ts +3 -3
- package/dist/renderers/basic/html-elements.d.ts +1 -1
- package/dist/renderers/basic/record-picker.d.ts +10 -1
- package/dist/renderers/complex/data-table.d.ts +100 -4
- package/dist/renderers/form/validationRuleKeys.d.ts +45 -0
- package/dist/renderers/layout/containers.d.ts +2 -1
- package/dist/renderers/layout/react-page.d.ts +12 -2
- package/dist/share/index.d.ts +2 -2
- package/dist/ui/calendar.d.ts +1 -1
- package/dist/ui/carousel.d.ts +2 -2
- package/dist/ui/command.d.ts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/index.d.ts +47 -47
- package/dist/ui/pagination.d.ts +1 -1
- package/dist/ui/sidebar.d.ts +2 -2
- package/dist/ui/slider.d.ts +2 -1
- package/package.json +14 -14
- package/dist/custom/action-param-dialog.d.ts +0 -21
- package/dist/renderers/data-display/table.d.ts +0 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DomProps as CoreDomProps } from '@object-ui/core';
|
|
2
|
+
/**
|
|
3
|
+
* The DOM pass-through declaration for the `packages/components` renderers whose
|
|
4
|
+
* host element is a FORM CONTROL (objectui#5632).
|
|
5
|
+
*
|
|
6
|
+
* ## Why this exists at all, rather than a bare `toDomProps`
|
|
7
|
+
*
|
|
8
|
+
* `toDomProps` is the SDUI contract's executor and its key list is deliberately
|
|
9
|
+
* ELEMENT-AGNOSTIC — it cannot know what element the widget renders, so it
|
|
10
|
+
* carries only what is legal on every element. `name` and `disabled` are legal
|
|
11
|
+
* on form controls and nowhere else, which is exactly why `dom-props.ts` leaves
|
|
12
|
+
* them out: `name` on the `<div>` a container widget renders is one of the 14
|
|
13
|
+
* attributes objectui#4431 closed.
|
|
14
|
+
*
|
|
15
|
+
* The renderers in this group render the other case. Routing them through the
|
|
16
|
+
* bare SDUI list would strip `name` and `disabled` off real controls — and
|
|
17
|
+
* `packages/fields` already wrote down what that costs, in the same words, for
|
|
18
|
+
* the same two keys:
|
|
19
|
+
*
|
|
20
|
+
* > Withholding them would make this helper a silent styling- and
|
|
21
|
+
* > interactivity-dropper … That is a NEW silent failure of exactly the kind
|
|
22
|
+
* > the whitelist exists to prevent, so they are forwarded.
|
|
23
|
+
*
|
|
24
|
+
* It is worth being precise about how INVISIBLE that failure would be here.
|
|
25
|
+
* The gate that grades this change
|
|
26
|
+
* (`packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx`) reports
|
|
27
|
+
* attributes that ARRIVE illegitimately; it has no case for an attribute that
|
|
28
|
+
* stops arriving, and its canary node authors no `disabled` at all. So dropping
|
|
29
|
+
* `disabled` off every form control in the library would have moved not one
|
|
30
|
+
* number in this card's own acceptance evidence. The ledger predicted the same
|
|
31
|
+
* thing from the other side — its `BARE_SPREAD_MINUS_NAME` note says converging
|
|
32
|
+
* this group "will not change that attribute, only the thirteen around it" —
|
|
33
|
+
* and that prediction is only true if `name` is forwarded deliberately.
|
|
34
|
+
*
|
|
35
|
+
* ## One mechanism, three declarations
|
|
36
|
+
*
|
|
37
|
+
* This is the third declaration over the ONE mechanism in
|
|
38
|
+
* `@object-ui/core`'s `pickDomProps`, and it follows the pattern
|
|
39
|
+
* `@object-ui/fields` established rather than inventing one: the consuming
|
|
40
|
+
* package owns the key list its own contract implies, and calls the shared
|
|
41
|
+
* filter with it. The three, and what separates them:
|
|
42
|
+
*
|
|
43
|
+
* - `toDomProps` (`@object-ui/core`) — the SDUI baseline, element-agnostic.
|
|
44
|
+
* - `@object-ui/fields`' `toDomProps` — the baseline minus `role`, plus `name`
|
|
45
|
+
* and `disabled`; `FieldWidgetComponentProps` declares the closed set.
|
|
46
|
+
* - this one — the baseline PLUS `name` and `disabled`, nothing removed. It is
|
|
47
|
+
* a strict superset of the SDUI list, which the compile-time assertion below
|
|
48
|
+
* pins, so a key added to the shared set can never be quietly dropped here.
|
|
49
|
+
*
|
|
50
|
+
* Anything beyond this set is still the objectui#4435 route: DECLARE it and
|
|
51
|
+
* forward it by name. Do not reopen the spread (AGENTS.md #0.1 — fix the
|
|
52
|
+
* contract, never widen the consumer). `style` is the standing example, and it
|
|
53
|
+
* is forwarded by name at every call site for exactly that reason.
|
|
54
|
+
*/
|
|
55
|
+
declare const FORM_CONTROL_DOM_PASS_THROUGH_KEYS: readonly ["id", "className", "role", "tabIndex", "autoFocus", "onClick", "onBlur", "onFocus", "name", "disabled"];
|
|
56
|
+
type FormControlDomPassThroughKey = (typeof FORM_CONTROL_DOM_PASS_THROUGH_KEYS)[number];
|
|
57
|
+
/** The subset of `P` this declaration forwards, with each key's declared type. */
|
|
58
|
+
export type FormControlDomProps<P> = CoreDomProps<P, FormControlDomPassThroughKey>;
|
|
59
|
+
/**
|
|
60
|
+
* Keep only what may legitimately become an attribute on a FORM CONTROL, and
|
|
61
|
+
* drop everything else — the injected `schema`, the data-source adapter, the
|
|
62
|
+
* authored node's SDUI metadata, the flattened `props` container, and every
|
|
63
|
+
* declared prop the renderer already consumed off `schema`.
|
|
64
|
+
*
|
|
65
|
+
* Replaces the bare `{...rest}` spread in a form-control registration:
|
|
66
|
+
*
|
|
67
|
+
* ```tsx
|
|
68
|
+
* // before — forwards whatever `SchemaRenderer` handed it
|
|
69
|
+
* const { style, ...rest } = props;
|
|
70
|
+
* return <Input name={schema.name} {...rest} />;
|
|
71
|
+
*
|
|
72
|
+
* // after
|
|
73
|
+
* return <Input name={schema.name} {...toFormControlDomProps(rest)} style={style} />;
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function toFormControlDomProps<P extends object>(props: P): FormControlDomProps<P>;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
* Reads the persisted sidebar collapse state back out of `document.cookie` —
|
|
10
|
+
* objectui#4234.
|
|
11
|
+
*
|
|
12
|
+
* `SidebarProvider` has always WRITTEN `sidebar_state` on every toggle (7-day
|
|
13
|
+
* max-age) and never read it back, so a collapsed sidebar came back expanded
|
|
14
|
+
* on the next load: the cookie was present and correct, and nothing consulted
|
|
15
|
+
* it. Upstream Shadcn closes that loop in a **server component** — it reads the
|
|
16
|
+
* cookie there and hands the value down as `defaultOpen`. A pure SPA (the
|
|
17
|
+
* console) has no such step, so the read has to happen client-side, which is
|
|
18
|
+
* what this function is for.
|
|
19
|
+
*
|
|
20
|
+
* ## Why the implementation lives HERE and not in the primitive
|
|
21
|
+
*
|
|
22
|
+
* `packages/components/src/ui/**` is regenerated from the Shadcn registry
|
|
23
|
+
* (AGENTS.md Commandment #7) — anything written there is overwritten by the
|
|
24
|
+
* next `pnpm shadcn:update`. `src/lib/` is not part of that regeneration, so
|
|
25
|
+
* this file is permanent. `sidebar.tsx` gets only a one-line call, re-applied
|
|
26
|
+
* on every sync by the declared patch in `scripts/shadcn-local-patches.mjs`.
|
|
27
|
+
* Keeping the payload out of the regenerated zone is what makes the patch small
|
|
28
|
+
* enough to survive upstream churn — the same shape as `close-label.tsx`.
|
|
29
|
+
*
|
|
30
|
+
* ## Why the cookie NAME is a parameter
|
|
31
|
+
*
|
|
32
|
+
* The name is owned by the write side: `SIDEBAR_COOKIE_NAME` in `sidebar.tsx`,
|
|
33
|
+
* the same constant the `document.cookie =` assignment interpolates. Passing it
|
|
34
|
+
* in keeps that the single source of truth — a second spelling here could drift
|
|
35
|
+
* from the writer and would read a cookie nobody writes. (Importing the
|
|
36
|
+
* constant instead would make `sidebar.tsx` and this module circular.)
|
|
37
|
+
*
|
|
38
|
+
* ## Strict values, deliberately
|
|
39
|
+
*
|
|
40
|
+
* Only the two literals the write side produces are accepted. Anything else —
|
|
41
|
+
* absent, empty, `"1"`, `"collapsed"`, a truncated value — returns `undefined`
|
|
42
|
+
* so the caller falls through to its `defaultOpen`. That is contract-first
|
|
43
|
+
* (AGENTS.md #0.1): the writer is ours and emits exactly `true`/`false`, so a
|
|
44
|
+
* tolerant parse here would only serve values no part of this system writes,
|
|
45
|
+
* and would silently invent a preference the user never expressed.
|
|
46
|
+
*
|
|
47
|
+
* @param name Cookie name to look up, matched EXACTLY (not by prefix or
|
|
48
|
+
* substring), so `sidebar_state_v2` / `my_sidebar_state` never answer for
|
|
49
|
+
* `sidebar_state`.
|
|
50
|
+
* @returns `true` / `false` for a persisted preference, or `undefined` when
|
|
51
|
+
* there is none to honour — including on the server, where there is no
|
|
52
|
+
* `document` to read (Next.js SSRs `"use client"` components for the initial
|
|
53
|
+
* HTML, so this guard is load-bearing for `apps/site`, not defensive noise).
|
|
54
|
+
*/
|
|
55
|
+
export declare function readSidebarStateCookie(name: string): boolean | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
import type * as React from 'react';
|
|
9
|
+
/**
|
|
10
|
+
* The payload of the `slider-thumb-pass-through` local patch
|
|
11
|
+
* (`scripts/shadcn-local-patches.mjs`, objectui#3318).
|
|
12
|
+
*
|
|
13
|
+
* ## The defect this exists for
|
|
14
|
+
*
|
|
15
|
+
* A Radix slider's focusable control is the THUMB — `span[role="slider"]`,
|
|
16
|
+
* `tabindex="0"`, the element that carries `aria-valuenow` and the one a
|
|
17
|
+
* keyboard user and their screen reader actually land on. `Slider.Root` renders
|
|
18
|
+
* a role-less wrapper `span` and forwards every unrecognised prop to it, so a
|
|
19
|
+
* host's control-channel facts (`id`, `aria-invalid`, `aria-describedby`,
|
|
20
|
+
* `aria-labelledby`, `aria-required`) landed on a wrapper assistive technology
|
|
21
|
+
* never visits. Measured on `origin/main`, a real form + a required slider that
|
|
22
|
+
* had just failed validation:
|
|
23
|
+
*
|
|
24
|
+
* ```
|
|
25
|
+
* slider ariaInvalidTrue=[] labelFor=…-form-item -> DANGLING
|
|
26
|
+
* descConsumers=0 focusable=[span[slider],input]
|
|
27
|
+
* ids=[] <- no element of the row carried an id at all
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* This is the same shape objectui#3306 fixed for `select`, one step harder:
|
|
31
|
+
* there the focusable control is `SelectTrigger`, a component the primitive
|
|
32
|
+
* EXPORTS, so the widget could address it directly. `ui/slider.tsx` renders its
|
|
33
|
+
* thumb internally and exports only `Slider`, so the widget has no handle on
|
|
34
|
+
* the one element that must carry the state — hence a pass-through.
|
|
35
|
+
*
|
|
36
|
+
* ## Why a declared prop and not an implicit bridge
|
|
37
|
+
*
|
|
38
|
+
* The primitive could instead have COPIED a fixed list of `aria-*` keys from
|
|
39
|
+
* its own props down to the thumb. Rejected: the copy would leave the same keys
|
|
40
|
+
* on Root as well, and for `id` that is not a duplicate attribute but a
|
|
41
|
+
* duplicate DOM ID — `document.getElementById` answers with the wrapper, so a
|
|
42
|
+
* host `<label for>` would resolve to the non-focusable span and the bug would
|
|
43
|
+
* survive its own fix wearing a green test. Splitting is therefore mandatory,
|
|
44
|
+
* and once the caller must say which keys go where, saying it explicitly is
|
|
45
|
+
* strictly better than a list the caller cannot see (AGENTS.md #0.1: one strict
|
|
46
|
+
* contract, no consumer-side guessing).
|
|
47
|
+
*
|
|
48
|
+
* ## Where the split is decided
|
|
49
|
+
*
|
|
50
|
+
* Not here — this only routes. The widget decides, and `SliderField` mirrors
|
|
51
|
+
* `SelectField`'s #3306 split exactly: `name` and `disabled` stay on Root (Root
|
|
52
|
+
* owns the hidden form input and is the single disabled authority), everything
|
|
53
|
+
* else in the DOM pass-through whitelist goes to the thumb.
|
|
54
|
+
*/
|
|
55
|
+
/** Props the `Slider` wrapper hands to its internal `SliderPrimitive.Thumb`. */
|
|
56
|
+
export interface SliderThumbPassThrough {
|
|
57
|
+
/**
|
|
58
|
+
* Forwarded verbatim onto the focusable `span[role="slider"]`.
|
|
59
|
+
*
|
|
60
|
+
* Deliberately typed as the DOM attribute surface rather than
|
|
61
|
+
* `ComponentPropsWithoutRef<typeof SliderPrimitive.Thumb>`: this module must
|
|
62
|
+
* not import the primitive (it is consumed BY the no-touch file, and a cycle
|
|
63
|
+
* through it would be a fragile thing to hang a sync patch on), and the thumb
|
|
64
|
+
* is a plain `span` as far as every key a host delivers is concerned.
|
|
65
|
+
*/
|
|
66
|
+
thumbProps?: React.HTMLAttributes<HTMLSpanElement> & {
|
|
67
|
+
id?: string;
|
|
68
|
+
tabIndex?: number;
|
|
69
|
+
'aria-label'?: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Route a `Slider`'s props to the two elements that need them.
|
|
74
|
+
*
|
|
75
|
+
* `thumbProps` is REMOVED from the root half — leaving it there would put
|
|
76
|
+
* `thumbprops="[object Object]"` on the wrapper span, which is exactly the leak
|
|
77
|
+
* objectui#3291's sweep exists to catch.
|
|
78
|
+
*
|
|
79
|
+
* The pre-existing `aria-label` bridge is preserved and applied FIRST, so a
|
|
80
|
+
* caller that passes both keeps the explicit `thumbProps` value; nothing about
|
|
81
|
+
* the attribute's previous behaviour changes for callers that pass neither.
|
|
82
|
+
*/
|
|
83
|
+
export declare function splitSliderThumbProps<P extends Record<string, unknown>>(props: P): {
|
|
84
|
+
root: Omit<P, 'thumbProps'>;
|
|
85
|
+
thumb: Record<string, unknown>;
|
|
86
|
+
};
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* Every one of these used to be handed to `onToast` regardless of type, so all
|
|
20
20
|
* five looked identical (#3014).
|
|
21
21
|
*/
|
|
22
|
-
export { NotificationBanners, type NotificationBannersProps } from './NotificationBanners';
|
|
23
|
-
export { NotificationSnackbar, type NotificationSnackbarProps } from './NotificationSnackbar';
|
|
24
|
-
export { NotificationAlerts, type NotificationAlertsProps } from './NotificationAlerts';
|
|
25
|
-
export { NotificationInline, type NotificationInlineProps } from './NotificationInline';
|
|
26
|
-
export { NOTIFICATION_ACTION_BUTTON_VARIANTS, NOTIFICATION_POSITION_CLASSES, NOTIFICATION_SEVERITY_STYLES, notificationActionVariant, notificationIcon, notificationSeverityStyle, type NotificationSeverityStyle, } from './severity';
|
|
22
|
+
export { NotificationBanners, type NotificationBannersProps } from './NotificationBanners.js';
|
|
23
|
+
export { NotificationSnackbar, type NotificationSnackbarProps } from './NotificationSnackbar.js';
|
|
24
|
+
export { NotificationAlerts, type NotificationAlertsProps } from './NotificationAlerts.js';
|
|
25
|
+
export { NotificationInline, type NotificationInlineProps } from './NotificationInline.js';
|
|
26
|
+
export { NOTIFICATION_ACTION_BUTTON_VARIANTS, NOTIFICATION_POSITION_CLASSES, NOTIFICATION_SEVERITY_STYLES, notificationActionVariant, notificationIcon, notificationSeverityStyle, type NotificationSeverityStyle, } from './severity.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UIActionSchema, ActionLocation } from '@object-ui/types';
|
|
2
2
|
export interface ActionBarSchema {
|
|
3
3
|
type: 'action:bar';
|
|
4
4
|
/** Business actions to render — subject to inline/overflow split via {@link maxVisible} */
|
|
5
|
-
actions?:
|
|
5
|
+
actions?: UIActionSchema[];
|
|
6
6
|
/**
|
|
7
7
|
* System/chrome actions (Duplicate, Export, View History, Delete, etc.) that
|
|
8
8
|
* are *always* placed in the overflow menu — never inline — regardless of
|
|
@@ -13,7 +13,7 @@ export interface ActionBarSchema {
|
|
|
13
13
|
* The first system action is automatically separated from business-overflow
|
|
14
14
|
* entries by a menu separator.
|
|
15
15
|
*/
|
|
16
|
-
systemActions?:
|
|
16
|
+
systemActions?: UIActionSchema[];
|
|
17
17
|
/** Filter actions by this location */
|
|
18
18
|
location?: ActionLocation;
|
|
19
19
|
/** Maximum visible inline actions before overflow into "More" menu (default: 3) */
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UIActionSchema } from '@object-ui/types';
|
|
2
|
+
/**
|
|
3
|
+
* The declared props. `schema` is `UIActionSchema` (objectui#4418): every key
|
|
4
|
+
* this renderer forwards below — `target`, `endpoint`, `bodyExtra`,
|
|
5
|
+
* `bodyShape`, `locations`, `enabled`, `size` — is declared by the modern type
|
|
6
|
+
* and by no other, and the legacy `crud.ts` `ActionSchema` it used to name is
|
|
7
|
+
* `@deprecated` and requires `type: 'action'` (this renderer serves
|
|
8
|
+
* `'script' | 'url' | 'modal' | 'flow' | 'api'`). `actionType` stays on the
|
|
9
|
+
* intersection: it is the legacy-shaped override this renderer reads FIRST
|
|
10
|
+
* (`schema.actionType || schema.type`), and it is not a `UIActionSchema` key.
|
|
11
|
+
*/
|
|
2
12
|
export interface ActionButtonProps {
|
|
3
|
-
schema:
|
|
13
|
+
schema: UIActionSchema & {
|
|
4
14
|
type: string;
|
|
5
15
|
className?: string;
|
|
6
16
|
actionType?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { UIActionSchema, ActionLocation } from '@object-ui/types';
|
|
3
3
|
export interface ActionGroupSchema {
|
|
4
4
|
type: 'action:group';
|
|
5
5
|
/** Group name */
|
|
@@ -9,7 +9,7 @@ export interface ActionGroupSchema {
|
|
|
9
9
|
/** Group icon */
|
|
10
10
|
icon?: string;
|
|
11
11
|
/** Actions in this group */
|
|
12
|
-
actions?:
|
|
12
|
+
actions?: UIActionSchema[];
|
|
13
13
|
/** Display mode: inline button row or dropdown */
|
|
14
14
|
display?: 'dropdown' | 'inline';
|
|
15
15
|
/** Filter actions by location */
|
|
@@ -35,7 +35,13 @@ export interface ActionGroupSchema {
|
|
|
35
35
|
* showed even when its predicate was false.
|
|
36
36
|
*/
|
|
37
37
|
export declare const DropdownActionItem: React.FC<{
|
|
38
|
-
action:
|
|
38
|
+
action: UIActionSchema;
|
|
39
39
|
index: number;
|
|
40
|
-
onSelect: (action:
|
|
40
|
+
onSelect: (action: UIActionSchema) => void | Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* The row this group is mounted over, forwarded by the host. Optional: an
|
|
43
|
+
* object-level group genuinely has no row, and a predicate over an empty
|
|
44
|
+
* record is still evaluable (objectui#4075).
|
|
45
|
+
*/
|
|
46
|
+
record?: unknown;
|
|
41
47
|
}>;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { UIActionSchema } from '@object-ui/types';
|
|
2
|
+
/**
|
|
3
|
+
* The declared props. `schema` is `UIActionSchema` (objectui#4418) for the same
|
|
4
|
+
* reason as `action:button`'s: `target`, `endpoint`, `bodyExtra`, `bodyShape`,
|
|
5
|
+
* `locations`, `enabled` and `size` are all modern-only keys this renderer
|
|
6
|
+
* forwards, and the legacy `crud.ts` `ActionSchema` is `@deprecated` and pins
|
|
7
|
+
* `type: 'action'` where this renderer's own registry `inputs` declare
|
|
8
|
+
* `'script' | 'url' | 'modal' | 'flow' | 'api'`.
|
|
9
|
+
*/
|
|
2
10
|
export interface ActionIconProps {
|
|
3
|
-
schema:
|
|
11
|
+
schema: UIActionSchema & {
|
|
4
12
|
type: string;
|
|
5
13
|
className?: string;
|
|
6
14
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { UIActionSchema } from '@object-ui/types';
|
|
2
3
|
export interface ActionMenuSchema {
|
|
3
4
|
type: 'action:menu';
|
|
4
5
|
/** Menu trigger label (defaults to icon-only) */
|
|
@@ -6,7 +7,7 @@ export interface ActionMenuSchema {
|
|
|
6
7
|
/** Menu trigger icon (defaults to more-horizontal) */
|
|
7
8
|
icon?: string;
|
|
8
9
|
/** Actions to render as menu items */
|
|
9
|
-
actions?:
|
|
10
|
+
actions?: UIActionSchema[];
|
|
10
11
|
/** Trigger variant */
|
|
11
12
|
variant?: string;
|
|
12
13
|
/** Trigger size */
|
|
@@ -17,3 +18,20 @@ export interface ActionMenuSchema {
|
|
|
17
18
|
className?: string;
|
|
18
19
|
[key: string]: any;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* One action inside an `action:menu`. Exported for its pin tests only (it is
|
|
23
|
+
* not re-exported from the package index) — mirrors `DropdownActionItem` in
|
|
24
|
+
* `action-group.tsx`, whose gate is the same one.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ActionMenuItem: React.FC<{
|
|
27
|
+
action: UIActionSchema;
|
|
28
|
+
onExecute: (action: UIActionSchema) => Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* The row this menu is mounted over, forwarded by the host. Optional: an
|
|
31
|
+
* object-level menu genuinely has no row, and a predicate over an empty
|
|
32
|
+
* record is still evaluable (objectui#4075 — the failure being fixed is a
|
|
33
|
+
* predicate that FAULTS on an unbound root, not one that reads a missing
|
|
34
|
+
* field).
|
|
35
|
+
*/
|
|
36
|
+
record?: unknown;
|
|
37
|
+
}>;
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
* Is this action asking to be auto-triggered? One spelling of the test, so the
|
|
10
|
+
* flag cannot be read as `!== undefined` in one renderer and `=== true` in
|
|
11
|
+
* another. Deliberately strict: only the literal `true` arms it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function hasAutoTrigger(action: unknown): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Run `run` at most once, as soon as `armed` is true.
|
|
16
|
+
*
|
|
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.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useAutoTriggerOnce(armed: boolean, run: () => void | Promise<void>): void;
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
* Did this action DECLARE a visibility gate at all? The renderer-side name for
|
|
10
|
+
* the repo's one definition of that question, which now lives one layer down in
|
|
11
|
+
* `@object-ui/core` as `hasDeclaredPredicate`
|
|
12
|
+
* (`packages/core/src/evaluator/declaredPredicate.ts`) — read the reasoning
|
|
13
|
+
* there; only what is specific to this file is repeated below.
|
|
14
|
+
*
|
|
15
|
+
* ## Why this module is now a re-export (objectui#3850 / objectui#3862)
|
|
16
|
+
*
|
|
17
|
+
* The question was answered three times with three different scopes, and the
|
|
18
|
+
* widest-scoped answer was on the key where the mistake is not benign:
|
|
19
|
+
*
|
|
20
|
+
* - HERE, `!= null && !== ''` — so every OBJECT counted as a declared gate,
|
|
21
|
+
* including `{ dialect: 'cel', source: '' }`. That envelope is not an exotic
|
|
22
|
+
* spelling: `@objectstack/spec`'s `ExpressionInputSchema` normalizes every
|
|
23
|
+
* authored predicate into one, so "the author left the predicate empty"
|
|
24
|
+
* compiles to exactly it. The verdict path then normalized the same value to
|
|
25
|
+
* `undefined` and `evaluateCondition(undefined)` answered `true` = "no
|
|
26
|
+
* condition, so visible/enabled" — which on `disabled` means GREYED OUT.
|
|
27
|
+
* A button disabled forever, indistinguishable from the metadata's intent
|
|
28
|
+
* (objectui#3850).
|
|
29
|
+
* - `SchemaRenderer`'s inline `!== undefined`, one notch wider again, so
|
|
30
|
+
* `disabled: null` greyed out any node on the generic rendering path
|
|
31
|
+
* (objectui#3862).
|
|
32
|
+
* - `ActionRunner`'s module-private helper, which asked "does this normalize
|
|
33
|
+
* to something evaluable?" — the scope objectui#3850 then ruled to be THE
|
|
34
|
+
* scope (objectui#3848).
|
|
35
|
+
*
|
|
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.
|
|
40
|
+
*
|
|
41
|
+
* The NAME is kept (`hasDeclaredVisibilityGate`) and this module keeps
|
|
42
|
+
* re-exporting it: the five member-action call sites — `action:button`,
|
|
43
|
+
* `action:icon`, `action:group`'s inline button and dropdown item, and
|
|
44
|
+
* `action:menu`'s item, plus `DeclaredActionsBar` and `record-quick-actions`
|
|
45
|
+
* through the `@object-ui/components` barrel — are unchanged by this move. The
|
|
46
|
+
* name is historic (objectui#3492 arrived through `visible`) and the predicate is
|
|
47
|
+
* key-neutral, which the call-site comments already say.
|
|
48
|
+
*
|
|
49
|
+
* ## What did NOT change: truthiness still cannot answer this
|
|
50
|
+
*
|
|
51
|
+
* `if (action.visible && !isVisible)` was the objectui#3812 defect: it
|
|
52
|
+
* classified `visible: false` — the most explicit way an author can say "never
|
|
53
|
+
* show this" — as *ungated*, so the gate never consulted the verdict and the
|
|
54
|
+
* action rendered for everyone. A declared boolean is still a declared gate
|
|
55
|
+
* here; only the empty and non-predicate shapes moved.
|
|
56
|
+
*
|
|
57
|
+
* ## The scope change is not verdict-neutral on every key — measured
|
|
58
|
+
*
|
|
59
|
+
* On `visible` the empty shapes were mostly benign already, because the two
|
|
60
|
+
* mistakes cancelled: over-broad "declared" plus `evaluateCondition` answering
|
|
61
|
+
* `true` for an empty predicate = SHOWN, the same result as "no gate". Measured
|
|
62
|
+
* on this tree (`action:button`, before → after):
|
|
63
|
+
*
|
|
64
|
+
* | value | `visible` | `disabled` | `enabled` |
|
|
65
|
+
* |------------------------------|-------------------|-------------------|-------------------|
|
|
66
|
+
* | `''` | shown → shown | on → on | on → on |
|
|
67
|
+
* | `{ dialect:'cel', source:'' }`| shown → shown | GREY → on | on → on |
|
|
68
|
+
* | `{ source: '' }` | shown → shown | GREY → on | on → on |
|
|
69
|
+
* | `' '` (whitespace) | HIDDEN → shown | on → on | GREY → on |
|
|
70
|
+
* | `0` / `{}` (not a predicate) | shown → shown | GREY → on | on → on |
|
|
71
|
+
* | `true` / `false` / CEL / `${…}`| unchanged | unchanged | unchanged |
|
|
72
|
+
*
|
|
73
|
+
* The whitespace row is the one that moves on `visible`, and it moves toward the
|
|
74
|
+
* invariant rather than away from it: `' '` used to be normalized to `'${ }'`
|
|
75
|
+
* (the normalizer wraps it rather than folding it), which evaluates falsy, so a
|
|
76
|
+
* predicate that says nothing HID the action from everyone. "An empty predicate
|
|
77
|
+
* must not hide the action from everyone" is what this gate has always claimed —
|
|
78
|
+
* the whitespace spelling simply never obeyed it. Same story on the negated
|
|
79
|
+
* `enabled` leg, where it greyed the control out instead.
|
|
80
|
+
*/
|
|
81
|
+
export { hasDeclaredPredicate as hasDeclaredVisibilityGate } from '@object-ui/core';
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
* - element:divider (no-props separator)
|
|
16
16
|
* - ElementButtonProps -> element:button
|
|
17
17
|
*
|
|
18
|
-
* Heavier interactive elements (element:
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
|
@@ -7,7 +7,7 @@
|
|
|
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
|
|
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
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* record of an object and writes the selection into a page variable.
|
|
10
10
|
*
|
|
11
11
|
* Data binding follows the spec's ElementDataSource (`schema.dataSource`):
|
|
12
|
-
* { object, filter?, sort?, limit? }
|
|
12
|
+
* { object, view?, filter?, sort?, limit? }
|
|
13
13
|
* with `properties.object` accepted as a fallback. Display config is read off
|
|
14
14
|
* `schema.properties`:
|
|
15
15
|
* { labelField='name', valueField='id', label?, placeholder?, emptyText? }
|
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
* picker is uncontrolled (still usable, just inert) so it never throws outside
|
|
21
21
|
* a Page. The written value drives any predicate referencing `page.<var>`
|
|
22
22
|
* (e.g. another component's `visible` / `visibility`).
|
|
23
|
+
*
|
|
24
|
+
* `view` is resolved through {@link useElementDataSource} rather than read off
|
|
25
|
+
* the binding directly (objectstack#6953). This block used to take `object` /
|
|
26
|
+
* `filter` / `sort` / `limit` off `schema.dataSource` and DROP `view`, so
|
|
27
|
+
* `dataSource: { object: 'account', view: 'hot' }` — the spec's own example —
|
|
28
|
+
* built an unfiltered picker over every account instead of the rows the saved
|
|
29
|
+
* view selects. That symptom is quieter than the one objectstack#5576 fixed on
|
|
30
|
+
* `list-view`: nothing errors, the list is simply WIDER than what was authored,
|
|
31
|
+
* which is exactly the failure an AI-authored page hides best.
|
|
23
32
|
*/
|
|
24
33
|
import * as React from 'react';
|
|
25
34
|
declare function ElementRecordPickerRenderer({ schema }: {
|
|
@@ -7,6 +7,101 @@ import { DataTableSchema } from '@object-ui/types';
|
|
|
7
7
|
* only reachable structurally through `DataTableSchema`.
|
|
8
8
|
*/
|
|
9
9
|
type RowActionDef = NonNullable<DataTableSchema['rowActionDefs']>[number];
|
|
10
|
+
/**
|
|
11
|
+
* The authoring shape of the built-in row Edit/Delete predicates — derived from
|
|
12
|
+
* `DataTableSchema`, never restated by hand (objectui#4354). Everything below
|
|
13
|
+
* reads what the production caller hands it (`schema.rowEditPredicates` /
|
|
14
|
+
* `rowDeletePredicates`), so a hand-written `{ visibleWhen?: unknown; … }` here
|
|
15
|
+
* would be a second definition of one authoring shape, free to drift from the
|
|
16
|
+
* type it is supposed to mirror without a single test going red.
|
|
17
|
+
*
|
|
18
|
+
* The union of the two twins is deliberate: every consumer below serves BOTH
|
|
19
|
+
* built-ins (`name: 'edit' | 'delete'`), so it may only read keys that BOTH
|
|
20
|
+
* schema keys declare — if either twin dropped one, the read stops compiling
|
|
21
|
+
* instead of silently reading `undefined`.
|
|
22
|
+
*/
|
|
23
|
+
type BuiltinRowPredicates = NonNullable<DataTableSchema['rowEditPredicates'] | DataTableSchema['rowDeletePredicates']>;
|
|
24
|
+
/**
|
|
25
|
+
* Does the built-in Edit/Delete item render for THIS row? The single
|
|
26
|
+
* definition, read both by the item itself and by the row-level guard that
|
|
27
|
+
* decides whether the "⋮" trigger exists at all. The two disagreeing is
|
|
28
|
+
* objectui#3562: the guard counted handlers, the items were then filtered by
|
|
29
|
+
* these predicates, and a row with every item suppressed still got a trigger
|
|
30
|
+
* that opened an empty 128×10 box.
|
|
31
|
+
*
|
|
32
|
+
* `visibleWhen` counts as a declared gate by `!= null`, not by truthiness —
|
|
33
|
+
* `visibleWhen: false` hides the item rather than reading as "ungated".
|
|
34
|
+
*
|
|
35
|
+
* The parameter is consumption-shaped — visibility is all this decides, so
|
|
36
|
+
* `disabledWhen` is deliberately absent — but `Pick`ed from the authoring type
|
|
37
|
+
* rather than hand-written, so it is a SUBSET of that type by construction
|
|
38
|
+
* (objectui#4354).
|
|
39
|
+
*/
|
|
40
|
+
export declare function isBuiltinRowActionVisible(predicates: Pick<BuiltinRowPredicates, 'visibleWhen'> | undefined, name: 'edit' | 'delete', row: any, scope: Record<string, unknown>): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Does this schema-driven custom row action render for THIS row? Same
|
|
43
|
+
* single-definition rule as the built-ins above.
|
|
44
|
+
*
|
|
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.
|
|
55
|
+
*
|
|
56
|
+
* `''` is grouped with `null` deliberately: an empty predicate is nothing to
|
|
57
|
+
* evaluate, so it must not hide the item from everyone either.
|
|
58
|
+
*/
|
|
59
|
+
export declare function isCustomRowActionVisible(action: {
|
|
60
|
+
name?: string;
|
|
61
|
+
visible?: unknown;
|
|
62
|
+
} | undefined, row: any, scope: Record<string, unknown>): boolean;
|
|
63
|
+
/** What the row overflow menu will actually render for ONE row. */
|
|
64
|
+
export interface DataTableRowMenuPlan {
|
|
65
|
+
/** The built-in Edit item renders for this row. */
|
|
66
|
+
edit: boolean;
|
|
67
|
+
/** The built-in Delete item renders for this row. */
|
|
68
|
+
remove: boolean;
|
|
69
|
+
/** Custom actions surviving their own `visible` predicate, in declared order. */
|
|
70
|
+
custom: RowActionDef[];
|
|
71
|
+
/** Items that will render. `0` means: render no trigger at all (#3562). */
|
|
72
|
+
count: number;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the row overflow menu for ONE row — which items survive their
|
|
76
|
+
* per-record predicates, and how many that leaves.
|
|
77
|
+
*
|
|
78
|
+
* Per ROW, not per table: `visibleWhen` / `visible` are per-record predicates,
|
|
79
|
+
* so two rows of the same table legitimately differ (one keeps Edit, the next
|
|
80
|
+
* has nothing left). A table-level guard cannot express that, which is why the
|
|
81
|
+
* trigger decision lives here and is recomputed for every row.
|
|
82
|
+
*
|
|
83
|
+
* Only visibility is decided here — a `disabled` item still renders (greyed
|
|
84
|
+
* out) and still counts, exactly as before. That counting is pinned where it
|
|
85
|
+
* can be OBSERVED, on the rendered menu, by
|
|
86
|
+
* `data-table-row-menu-empty-guard.test.tsx`'s "keeps the trigger for a row
|
|
87
|
+
* whose only item renders merely DISABLED".
|
|
88
|
+
*
|
|
89
|
+
* The predicate parameters are consumption-shaped — this function reads
|
|
90
|
+
* `visibleWhen` and nothing else, and the signature keeps saying so — but each
|
|
91
|
+
* is `Pick`ed from the authoring key its production caller passes, rather than
|
|
92
|
+
* hand-restated (objectui#4354). Same subset, minus the drift: a rename in
|
|
93
|
+
* `@object-ui/types` fails here at compile time instead of leaving a stale
|
|
94
|
+
* hand-copy that still type-checks against nothing.
|
|
95
|
+
*/
|
|
96
|
+
export declare function planDataTableRowMenu(input: {
|
|
97
|
+
onRowEdit?: unknown;
|
|
98
|
+
onRowDelete?: unknown;
|
|
99
|
+
editPredicates?: Pick<NonNullable<DataTableSchema['rowEditPredicates']>, 'visibleWhen'>;
|
|
100
|
+
deletePredicates?: Pick<NonNullable<DataTableSchema['rowDeletePredicates']>, 'visibleWhen'>;
|
|
101
|
+
customActions: readonly RowActionDef[];
|
|
102
|
+
row: any;
|
|
103
|
+
scope: Record<string, unknown>;
|
|
104
|
+
}): DataTableRowMenuPlan;
|
|
10
105
|
/**
|
|
11
106
|
* One schema-driven custom row action in the data-table's inline row overflow
|
|
12
107
|
* menu. Extracted into its own component so the action's `visible` (and
|
|
@@ -50,10 +145,11 @@ export declare const DataTableRowActionItem: React.FC<{
|
|
|
50
145
|
*/
|
|
51
146
|
export declare const DataTableBuiltinRowActionItem: React.FC<{
|
|
52
147
|
name: 'edit' | 'delete';
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The whole authoring pair, because this component reads both keys — derived
|
|
150
|
+
* from `DataTableSchema` rather than hand-copied (objectui#4354).
|
|
151
|
+
*/
|
|
152
|
+
predicates?: BuiltinRowPredicates;
|
|
57
153
|
row: any;
|
|
58
154
|
icon: React.ReactNode;
|
|
59
155
|
label: string;
|