@object-ui/components 17.7.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 +868 -0
- package/README.md +22 -0
- package/dist/custom/RecordTitleChip.d.ts +4 -3
- package/dist/custom/dialog-viewport-gutter.d.ts +37 -0
- package/dist/custom/empty-state-link.d.ts +63 -0
- package/dist/custom/filter-builder.d.ts +35 -19
- 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 -0
- package/dist/custom/sort-builder.d.ts +8 -0
- package/dist/custom/use-horizontal-overflow.d.ts +36 -0
- package/dist/hooks/related-count-store.d.ts +55 -0
- package/dist/index.css +194 -4
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5148 -3912
- package/dist/index.umd.cjs +6 -5
- package/dist/lib/inline-markdown.d.ts +64 -0
- package/dist/renderers/action/disabled-reason.d.ts +49 -0
- package/dist/renderers/action/static-params.d.ts +17 -25
- package/dist/renderers/basic/data-list.d.ts +2 -1
- package/dist/renderers/basic/elements.d.ts +2 -3
- package/dist/renderers/basic/record-picker.d.ts +3 -2
- package/dist/renderers/complex/dataTableBindDiagnostic.d.ts +2 -2
- package/dist/renderers/layout/containers.d.ts +30 -16
- 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/ui/button.d.ts +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -140,6 +140,28 @@ function MyComponent() {
|
|
|
140
140
|
- `tabs` - Tab navigation
|
|
141
141
|
- `accordion` - Collapsible sections
|
|
142
142
|
|
|
143
|
+
#### Page region posture — `usePageRegionPosture()`
|
|
144
|
+
|
|
145
|
+
The page renderer measures each region of a page against the page body, using
|
|
146
|
+
`ResizeObserver` on the region, not the viewport, and tells the region's blocks
|
|
147
|
+
how much room they have:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { usePageRegionPosture, type PageRegionPosture } from '@object-ui/components';
|
|
151
|
+
|
|
152
|
+
const posture: PageRegionPosture | null = usePageRegionPosture(); // 'compact' | 'full' | null
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `'compact'`: the region is narrower than `(page − 24) / 2`, that is, half
|
|
156
|
+
the page less the 24 px gap between regions side by side. A half-and-half
|
|
157
|
+
cell therefore stays `'full'`.
|
|
158
|
+
- `'full'`: the region is half the page or wider.
|
|
159
|
+
- `null`: outside a page region, or before the width is known (server render,
|
|
160
|
+
no `ResizeObserver`). Read `null` as `'full'`.
|
|
161
|
+
|
|
162
|
+
The posture is measured, never authored, and no schema key sets it. The
|
|
163
|
+
`list-view` block in `@object-ui/plugin-list` reads it to draw a compact list.
|
|
164
|
+
|
|
143
165
|
### Data Display
|
|
144
166
|
- `table` - Data table
|
|
145
167
|
- `list` - List view
|
|
@@ -8,13 +8,14 @@ export interface RecordTitleChipProps {
|
|
|
8
8
|
objectLabel?: string;
|
|
9
9
|
/** Record identifier shown next to the object label with a copy button. */
|
|
10
10
|
resourceId?: string;
|
|
11
|
-
/** Hide the favourite star (default: shown). */
|
|
12
|
-
showStar?: boolean;
|
|
13
11
|
/** Hide the copy-id button (default: shown when `resourceId` is given). */
|
|
14
12
|
showCopyId?: boolean;
|
|
15
13
|
/** Controlled favourite state. When omitted, the chip manages its own. */
|
|
16
14
|
isFavorite?: boolean;
|
|
17
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Called when the favourite star is toggled. The star draws exactly when
|
|
17
|
+
* this is given: a star with nothing to call would store nothing.
|
|
18
|
+
*/
|
|
18
19
|
onToggleFavorite?: (next: boolean) => void;
|
|
19
20
|
/** Extra classes for the outer flex column. */
|
|
20
21
|
className?: string;
|
|
@@ -0,0 +1,37 @@
|
|
|
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 ONE viewport gutter every wide centred dialog reads from `sm` up
|
|
10
|
+
* (objectui#12593, promoted to a shared export by objectui#12603).
|
|
11
|
+
*
|
|
12
|
+
* The `DialogContent` primitive draws `w-full max-w-lg`. A consumer that raises
|
|
13
|
+
* the cap with `sm:max-w-2xl` or wider makes the dialog exactly as wide as any
|
|
14
|
+
* window narrower than that cap, at x = 0, edge to edge. With this class the
|
|
15
|
+
* dialog is the smaller of its max width and the window less 2rem, and the
|
|
16
|
+
* primitive's `left-[50%]` plus `translate-x-[-50%]` centres it with an equal
|
|
17
|
+
* 1rem either side. Above the max width plus 2rem the dialog is exactly as wide
|
|
18
|
+
* as before.
|
|
19
|
+
*
|
|
20
|
+
* `100%`, not `100vw`: the dialog is `fixed`, so `100%` is its containing
|
|
21
|
+
* block, the viewport less any classic scrollbar, the same box `left-[50%]`
|
|
22
|
+
* centres in. `100vw` counts the scrollbar, so each gutter would lose half of
|
|
23
|
+
* the scrollbar's width.
|
|
24
|
+
*
|
|
25
|
+
* `sm:` only: below `sm` a dialog is the phone sheet, which this leaves alone.
|
|
26
|
+
* A dialog that keeps its own viewport-relative cap (`sm:max-w-[95vw]
|
|
27
|
+
* sm:w-full`, the `full` size) does not read it.
|
|
28
|
+
*
|
|
29
|
+
* The class string is literal here so Tailwind's source scan emits it; an app
|
|
30
|
+
* that `@source`-scans this package gets the rule. A site writes its wide cap
|
|
31
|
+
* and this constant together (`` `sm:max-w-5xl ${DIALOG_VIEWPORT_GUTTER}` ``,
|
|
32
|
+
* or the constant as an unconditional `cn()` argument), never a gutter literal
|
|
33
|
+
* of its own. The census
|
|
34
|
+
* `packages/components/src/__tests__/dialogViewportGutterCensus-12603.test.ts`
|
|
35
|
+
* enumerates every wide `DialogContent` and fails one that does not.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DIALOG_VIEWPORT_GUTTER = "sm:w-[calc(100%-2rem)]";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { EmptyState } from '@objectstack/spec/ui';
|
|
2
|
+
/**
|
|
3
|
+
* ObjectUI
|
|
4
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The one link an empty state names (objectui#12270).
|
|
11
|
+
*
|
|
12
|
+
* `@objectstack/spec`'s `EmptyStateSchema` declares an optional `link`,
|
|
13
|
+
* `{ label, url }`: the one next step an empty list names, such as the how-to
|
|
14
|
+
* page that explains what belongs here. The shape is taken by reference, so the
|
|
15
|
+
* link reaches every empty state that shape reaches, and each of them draws it
|
|
16
|
+
* through this component, ⛔ never a copy of its own:
|
|
17
|
+
*
|
|
18
|
+
* - a list view's `emptyState`, drawn by `@object-ui/plugin-list`'s `ListView`;
|
|
19
|
+
* - a list page's own `interfaceConfig.emptyState`, which that page hands the
|
|
20
|
+
* same `ListView`;
|
|
21
|
+
* - the `object-grid` block's `emptyState`, drawn by `@object-ui/plugin-grid`'s
|
|
22
|
+
* `ObjectGrid`.
|
|
23
|
+
*
|
|
24
|
+
* ## The click is the action runner's
|
|
25
|
+
*
|
|
26
|
+
* The link is followed as a `type: 'url'` action through the ActionRunner, so
|
|
27
|
+
* what a click may open is the runner's `classifyNavigationTarget` answer, the
|
|
28
|
+
* one answer every navigator in the app asks (ruling A+ on objectui#12191).
|
|
29
|
+
* ⛔ No scheme test lives here. An in-app path goes through the host's router,
|
|
30
|
+
* in place. An `http(s)://` address opens as the runner opens any external
|
|
31
|
+
* target. `mailto:` opens the mail client in place. Any other value is refused
|
|
32
|
+
* by the runner at the click, which reports its refusal (an error toast under
|
|
33
|
+
* the console's provider) and navigates nowhere. The spec judges the url at
|
|
34
|
+
* parse, except a template that opens with `${…}`, which it says is judged when
|
|
35
|
+
* it is clicked, after interpolation: the runner's click is that judgement, so
|
|
36
|
+
* nothing is judged here at render.
|
|
37
|
+
*
|
|
38
|
+
* Following a link is not feedback-worthy, so it raises no success toast, as
|
|
39
|
+
* the html tier's anchor and the dashboard's header link raise none.
|
|
40
|
+
*
|
|
41
|
+
* ## Without an ActionProvider
|
|
42
|
+
*
|
|
43
|
+
* `useAction()` then hands over a local runner with no navigation or toast
|
|
44
|
+
* handler. That runner hands an admitted in-app path back as `redirect`
|
|
45
|
+
* instead of navigating, and reports a refusal to nobody, so this component
|
|
46
|
+
* follows the redirect with a page navigation and logs the refusal.
|
|
47
|
+
*/
|
|
48
|
+
import * as React from 'react';
|
|
49
|
+
/** The spec's empty-state link, `{ label, url }` (`EmptyStateSchema.link`). */
|
|
50
|
+
export type EmptyStateLinkSpec = NonNullable<EmptyState['link']>;
|
|
51
|
+
export interface EmptyStateLinkProps {
|
|
52
|
+
/** The authored `emptyState.link`. Absent, nothing is drawn. */
|
|
53
|
+
link?: EmptyStateLinkSpec;
|
|
54
|
+
/**
|
|
55
|
+
* The locale the `label` is resolved against when it is an inline locale
|
|
56
|
+
* map: the one the caller resolves the empty state's `title` and `message`
|
|
57
|
+
* against. A label that resolves to no text draws nothing.
|
|
58
|
+
*/
|
|
59
|
+
displayLocale: string;
|
|
60
|
+
className?: string;
|
|
61
|
+
'data-testid'?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function EmptyStateLink({ link, displayLocale, className, 'data-testid': testId, }: EmptyStateLinkProps): React.JSX.Element | null;
|
|
@@ -17,11 +17,10 @@ import * as React from "react";
|
|
|
17
17
|
* and `operator` here is the builder's own dropdown vocabulary
|
|
18
18
|
* (`FILTER_BUILDER_OPERATORS`), not the spec's `$`-tokens.
|
|
19
19
|
*
|
|
20
|
-
* DERIVED from `@object-ui/types
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* each a named extension:
|
|
20
|
+
* DERIVED from `@object-ui/types`' `FilterBuilderCondition` (objectui#9306).
|
|
21
|
+
* That declaration is the one name authority for the row; this one takes every
|
|
22
|
+
* member from it (`id`, required since objectui#8415, and `field`) and restates
|
|
23
|
+
* exactly two, each a named extension:
|
|
25
24
|
*
|
|
26
25
|
* - `operator` — {@link FilterBuilderOperator}: the protocol's ids, which is
|
|
27
26
|
* what the authority declares, plus the opt-in `exists` / `notExists` the
|
|
@@ -33,15 +32,25 @@ import * as React from "react";
|
|
|
33
32
|
* below that takes a row's value (`reshapeFilterValue`, `retypeFilterValue`,
|
|
34
33
|
* `narrowFilterValueToOptions`, …) is typed on this member, so `any` here
|
|
35
34
|
* would switch their checking off in silence.
|
|
35
|
+
*
|
|
36
|
+
* Its own NAME, not the authority's (objectui#6349, batch 12). Under the
|
|
37
|
+
* authority's name this was a second declaration of `FilterBuilderCondition`
|
|
38
|
+
* with a different shape: the two are assignable in neither direction (the
|
|
39
|
+
* operator blocks one way, the value's requiredness the other), so an
|
|
40
|
+
* auto-import picked one of two rows by package. `FilterBuilderCondition` now
|
|
41
|
+
* means the authored row in `@object-ui/types` only, and this specialisation
|
|
42
|
+
* is named for the builder that edits it.
|
|
36
43
|
*/
|
|
37
|
-
export interface
|
|
44
|
+
export interface FilterBuilderRow extends Omit<AuthoredFilterBuilderCondition, "operator" | "value"> {
|
|
38
45
|
operator: FilterBuilderOperator;
|
|
39
46
|
value: string | number | boolean | (string | number | boolean)[];
|
|
40
47
|
}
|
|
41
48
|
/**
|
|
42
49
|
* The group the FilterBuilder edits: one `logic` over FLAT rows.
|
|
43
50
|
*
|
|
44
|
-
* DERIVED from `@object-ui/types` like the row above
|
|
51
|
+
* DERIVED from `@object-ui/types`' `FilterGroup` like the row above
|
|
52
|
+
* (objectui#9306), and named apart from it for the same reason
|
|
53
|
+
* (objectui#6349, batch 12): `FilterGroup` means the authored group only. Only
|
|
45
54
|
* `conditions` is restated, to hold THIS package's row; `id` and `logic` are
|
|
46
55
|
* the authority's own. So `id` is OPTIONAL here, as it is there: nothing in
|
|
47
56
|
* this builder reads a group's `id`, `onChange` hands back whatever group the
|
|
@@ -53,8 +62,8 @@ export interface FilterBuilderCondition extends Omit<AuthoredFilterBuilderCondit
|
|
|
53
62
|
* and has no control that creates a group. The authority retired nested
|
|
54
63
|
* sub-groups in the same change and its zod mirror refuses one by name.
|
|
55
64
|
*/
|
|
56
|
-
export interface
|
|
57
|
-
conditions:
|
|
65
|
+
export interface FilterBuilderRowGroup extends Omit<AuthoredFilterGroup, "conditions"> {
|
|
66
|
+
conditions: FilterBuilderRow[];
|
|
58
67
|
}
|
|
59
68
|
export interface FilterBuilderProps {
|
|
60
69
|
fields?: Array<{
|
|
@@ -72,8 +81,8 @@ export interface FilterBuilderProps {
|
|
|
72
81
|
/** ID field on the referenced object (defaults to "id") */
|
|
73
82
|
idField?: string;
|
|
74
83
|
}>;
|
|
75
|
-
value?:
|
|
76
|
-
onChange?: (value:
|
|
84
|
+
value?: FilterBuilderRowGroup;
|
|
85
|
+
onChange?: (value: FilterBuilderRowGroup) => void;
|
|
77
86
|
className?: string;
|
|
78
87
|
showClearAll?: boolean;
|
|
79
88
|
/**
|
|
@@ -298,7 +307,7 @@ export declare function filterValueArity(operator: string): FilterValueArity;
|
|
|
298
307
|
*
|
|
299
308
|
* @internal exported for tests
|
|
300
309
|
*/
|
|
301
|
-
export declare function reshapeFilterValue(value:
|
|
310
|
+
export declare function reshapeFilterValue(value: FilterBuilderRow["value"], nextOperator: string): FilterBuilderRow["value"];
|
|
302
311
|
/**
|
|
303
312
|
* The operator a row keeps after its FIELD is changed, given the operators the
|
|
304
313
|
* NEW field offers (objectui#4768).
|
|
@@ -428,7 +437,7 @@ export declare function mountedOperatorValue(operator: string, offeredOperators:
|
|
|
428
437
|
*
|
|
429
438
|
* @internal exported for tests
|
|
430
439
|
*/
|
|
431
|
-
export declare function retypeFilterValue(value:
|
|
440
|
+
export declare function retypeFilterValue(value: FilterBuilderRow["value"], fieldType: string | undefined, operator: string): FilterBuilderRow["value"];
|
|
432
441
|
/** The shape of a `fields` entry that the option-domain helpers below read. */
|
|
433
442
|
type OptionBearingField = {
|
|
434
443
|
type?: string;
|
|
@@ -476,7 +485,7 @@ type OptionBearingField = {
|
|
|
476
485
|
*
|
|
477
486
|
* @internal exported for tests
|
|
478
487
|
*/
|
|
479
|
-
export declare function narrowFilterValueToOptions(value:
|
|
488
|
+
export declare function narrowFilterValueToOptions(value: FilterBuilderRow["value"], field: OptionBearingField | undefined, operator: string): FilterBuilderRow["value"];
|
|
480
489
|
/**
|
|
481
490
|
* Is this row's value FINISHED for the operator it carries — the arity-aware
|
|
482
491
|
* reading of "the user has filled this in" (objectstack#8815).
|
|
@@ -528,10 +537,17 @@ export declare function narrowFilterValueToOptions(value: FilterBuilderCondition
|
|
|
528
537
|
*
|
|
529
538
|
* @internal exported for tests
|
|
530
539
|
*/
|
|
531
|
-
export declare function isFilterValueComplete(operator: string, value:
|
|
540
|
+
export declare function isFilterValueComplete(operator: string, value: FilterBuilderRow["value"] | undefined | null): boolean;
|
|
532
541
|
/**
|
|
533
|
-
* The operators
|
|
534
|
-
*
|
|
542
|
+
* The operators a row on a field of `fieldType` can HOLD, given the opt-in ids
|
|
543
|
+
* this instance was granted — every operator the dropdown can draw for it.
|
|
544
|
+
*
|
|
545
|
+
* What the dropdown OFFERS a fresh choice is this set minus the nullness pair
|
|
546
|
+
* on a type that cannot tell "empty" from "null" (objectui#11810, see
|
|
547
|
+
* {@link offeredOperatorsForRow}). That narrowing is deliberately NOT applied
|
|
548
|
+
* here: callers outside this file (`app-shell`'s dataset read-back,
|
|
549
|
+
* `plugin-list`'s parity pin) ask "can the builder hold this row", and a
|
|
550
|
+
* stored `is_null` on a `select` column still can.
|
|
535
551
|
*
|
|
536
552
|
* A pure function rather than a closure so the selection rule — and above all
|
|
537
553
|
* the {@link OPT_IN_OPERATORS} gate, whose whole job is to keep an operator OFF
|
|
@@ -557,9 +573,9 @@ interface LookupValuePickerProps {
|
|
|
557
573
|
displayField?: string;
|
|
558
574
|
idField?: string;
|
|
559
575
|
};
|
|
560
|
-
value:
|
|
576
|
+
value: FilterBuilderRow["value"];
|
|
561
577
|
multiple: boolean;
|
|
562
|
-
onChange: (value:
|
|
578
|
+
onChange: (value: FilterBuilderRow["value"]) => void;
|
|
563
579
|
}
|
|
564
580
|
declare function LookupValuePicker({ field, value, multiple, onChange }: LookupValuePickerProps): React.JSX.Element;
|
|
565
581
|
export { FilterBuilder, LookupValuePicker };
|
|
@@ -47,5 +47,9 @@ export interface GroupingEditorProps {
|
|
|
47
47
|
* Field options are filtered per row so a field can only appear in one level
|
|
48
48
|
* at a time. The current row's selected field stays in its own dropdown so the
|
|
49
49
|
* user sees the active selection.
|
|
50
|
+
*
|
|
51
|
+
* The field selector is the shared `Select` primitive, the control the Filter
|
|
52
|
+
* and Sort panels beside this one pick a field with, so the three panels look
|
|
53
|
+
* and behave alike (objectui#11865). It used to be a browser-native `<select>`.
|
|
50
54
|
*/
|
|
51
55
|
export declare function GroupingEditor({ value, onChange, fieldOptions, maxLevels, className, labels, }: GroupingEditorProps): React.JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface HorizontalOverflowBoxProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
|
+
/**
|
|
4
|
+
* Where the shadow is drawn: `edge` (default) overlays the box's right edge;
|
|
5
|
+
* `pinned` leaves it to the right-pinned cells inside, which carry
|
|
6
|
+
* `PINNED_RIGHT_SEAM_SHADOW_CLASSES` (`./use-horizontal-overflow`).
|
|
7
|
+
*/
|
|
8
|
+
seam?: 'edge' | 'pinned';
|
|
9
|
+
/** Classes for the positioned wrapper around the scrolling box. */
|
|
10
|
+
outerClassName?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A horizontally scrolling box with the shared overflow affordance. The
|
|
14
|
+
* scrolling element is the inner `div` (`className`, the ref and every other
|
|
15
|
+
* prop land on it); the edge shadow is its sibling, so it never scrolls away
|
|
16
|
+
* with the content.
|
|
17
|
+
*/
|
|
18
|
+
export declare const HorizontalOverflowBox: React.ForwardRefExoticComponent<HorizontalOverflowBoxProps & React.RefAttributes<HTMLDivElement>>;
|
package/dist/custom/index.d.ts
CHANGED
|
@@ -24,4 +24,8 @@ export * from './shimmer-skeleton.js';
|
|
|
24
24
|
export * from './RecordTitleChip.js';
|
|
25
25
|
export * from './refresh-indicator.js';
|
|
26
26
|
export * from './view-states.js';
|
|
27
|
+
export * from './empty-state-link.js';
|
|
27
28
|
export * from './mobile-dialog-content.js';
|
|
29
|
+
export * from './dialog-viewport-gutter.js';
|
|
30
|
+
export { HorizontalOverflowBox, type HorizontalOverflowBoxProps } from './horizontal-overflow.js';
|
|
31
|
+
export { PINNED_RIGHT_SEAM_SHADOW_CLASSES } from './use-horizontal-overflow.js';
|
|
@@ -26,6 +26,14 @@ export interface SortBuilderProps {
|
|
|
26
26
|
fields?: Array<{
|
|
27
27
|
value: string;
|
|
28
28
|
label: string;
|
|
29
|
+
/**
|
|
30
|
+
* Listed but not choosable (objectui#11943). A disabled entry still names
|
|
31
|
+
* a row whose current field it is, so that row shows its label and can be
|
|
32
|
+
* changed or removed, but no row's dropdown lets the user pick it and
|
|
33
|
+
* "Add sort" never seeds it. "Add sort" seeds the first entry that is not
|
|
34
|
+
* disabled, and is itself disabled when every entry is.
|
|
35
|
+
*/
|
|
36
|
+
disabled?: boolean;
|
|
29
37
|
}>;
|
|
30
38
|
value?: SortItem[];
|
|
31
39
|
onChange?: (value: SortItem[]) => void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The horizontal-overflow affordance every wide table shares (objectui#12708):
|
|
4
|
+
* a box that scrolls sideways says so with a right-edge shadow while columns
|
|
5
|
+
* remain to its right, and shows its scrollbar while the pointer is over it or
|
|
6
|
+
* focus is inside it. Built once, here, and used by `data-table`'s own scroll
|
|
7
|
+
* box, `ObjectGrid`'s grouped scroller and any table a page mounts itself.
|
|
8
|
+
*/
|
|
9
|
+
/** Whether `box` can scroll further to the right (one pixel of slack for sub-pixel layouts). */
|
|
10
|
+
export declare function hasOverflowRight(box: Pick<HTMLElement, 'scrollLeft' | 'clientWidth' | 'scrollWidth'>): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Track {@link hasOverflowRight} for the box `ref` points at: read in a layout
|
|
13
|
+
* effect, so the first painted frame is already right, and again on every
|
|
14
|
+
* scroll and whenever the box or its content changes size. `false` while
|
|
15
|
+
* `disabled` (the box does not scroll on its own).
|
|
16
|
+
*/
|
|
17
|
+
export declare function useHorizontalOverflow(ref: React.RefObject<HTMLElement | null>, { disabled }?: {
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
}): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Scrollbar colours for a box that scrolls: transparent until the pointer is
|
|
22
|
+
* over the box or focus is inside it, then the theme's muted foreground. The bar
|
|
23
|
+
* keeps its place, so nothing shifts when it appears. Marked important
|
|
24
|
+
* because a host may theme every scrollbar with an unlayered `*` rule (the
|
|
25
|
+
* console does), which outranks any layered utility.
|
|
26
|
+
*/
|
|
27
|
+
export declare const HOVER_SCROLLBAR_CLASSES = "[scrollbar-width:thin]! [scrollbar-color:transparent_transparent]! hover:[scrollbar-color:var(--color-muted-foreground)_transparent]! focus-within:[scrollbar-color:var(--color-muted-foreground)_transparent]!";
|
|
28
|
+
/**
|
|
29
|
+
* Classes for a RIGHT-PINNED cell inside a {@link HorizontalOverflowBox} with
|
|
30
|
+
* `seam="pinned"`: the cell draws the shadow on its own left edge, the seam
|
|
31
|
+
* where the scrolling columns slide under it, while the box can scroll further
|
|
32
|
+
* right. Needs no measurement, because the pinned cell is where the seam is.
|
|
33
|
+
* Spelled out in full (as the edge overlay below is) so Tailwind's source scan
|
|
34
|
+
* sees every utility.
|
|
35
|
+
*/
|
|
36
|
+
export declare const PINNED_RIGHT_SEAM_SHADOW_CLASSES = "before:pointer-events-none before:absolute before:inset-y-0 before:right-full before:w-6 before:bg-gradient-to-l before:from-foreground/10 before:to-transparent before:opacity-0 group-data-[overflow-right=true]/hscroll:before:opacity-100";
|
|
@@ -18,6 +18,11 @@ interface ProbeFn {
|
|
|
18
18
|
length?: number;
|
|
19
19
|
}>;
|
|
20
20
|
}
|
|
21
|
+
/** What one read of a related list answered: the server total and its rows. */
|
|
22
|
+
interface ReadResult {
|
|
23
|
+
total: number;
|
|
24
|
+
rows: unknown[];
|
|
25
|
+
}
|
|
21
26
|
/**
|
|
22
27
|
* A list's own declared scope, as carried on the `record:related_list` node it
|
|
23
28
|
* badges (objectui#4664 / objectstack#7118). Two probes over the SAME
|
|
@@ -32,6 +37,53 @@ declare function setCount(objectName: string, relField: string | undefined, pare
|
|
|
32
37
|
* for the same key and caches the resulting number until invalidated.
|
|
33
38
|
*/
|
|
34
39
|
declare function fetchCount(probe: ProbeFn, objectName: string, relField: string | undefined, parentId: string | undefined, filter?: CountScopeFilter, fields?: FieldContainerLike): Promise<number>;
|
|
40
|
+
/**
|
|
41
|
+
* objectui#13010 — the rail card's read: the first `limit` rows of the
|
|
42
|
+
* unfiltered related list and its total. Takes the rows a badge's read holds
|
|
43
|
+
* for this card's claim, joins a read in flight that asked for enough rows, or
|
|
44
|
+
* reads itself and registers that read in flight for a badge to join.
|
|
45
|
+
* Rejects when the read fails, so the card can show its error state.
|
|
46
|
+
*
|
|
47
|
+
* One exception to the registration: when a read with too FEW rows (a badge's
|
|
48
|
+
* one-row probe) is already in flight, this card's own read is NOT registered
|
|
49
|
+
* in flight. The too-small read stays the one a badge joins, and since only the
|
|
50
|
+
* current in-flight read installs a count, the card's own read installs none:
|
|
51
|
+
* the badge's read does.
|
|
52
|
+
*
|
|
53
|
+
* Contract: held rows are a HAND-OFF, not a cache. They are taken once (the
|
|
54
|
+
* first `fetchRows` that finds them removes them; a second call reads again),
|
|
55
|
+
* and they are dropped without being taken when the child object is
|
|
56
|
+
* invalidated (`invalidate` / `invalidateAll`, and the data-change bus that
|
|
57
|
+
* calls them), when the claim is released (`releaseRows`: the card unmounts or
|
|
58
|
+
* moves to a new scope), and when the read that fetched them was overtaken by
|
|
59
|
+
* either. The total is recorded in the store as `fetch` records it. A caller
|
|
60
|
+
* that must see data newer than the record open (a refresh) does not call this.
|
|
61
|
+
*/
|
|
62
|
+
declare function fetchRows(probe: ProbeFn, objectName: string, relField: string, parentId: string, limit: number, fields?: FieldContainerLike): Promise<ReadResult>;
|
|
63
|
+
/**
|
|
64
|
+
* objectui#13010 — a rail card says, while it renders, that it will list the
|
|
65
|
+
* first `limit` rows of this unfiltered related list. Idempotent, so a render
|
|
66
|
+
* React repeats or discards claims nothing new.
|
|
67
|
+
*
|
|
68
|
+
* Contract: a claim reads nothing by itself. While it stands, a `fetch` of the
|
|
69
|
+
* same unfiltered (object, relField, parentId) key asks for the claimed rows
|
|
70
|
+
* (the largest `limit` claimed) with its count and holds them for one
|
|
71
|
+
* `fetchRows` to take. A probe with a declared filter never meets a claim. The
|
|
72
|
+
* claim stands until `releaseRows` (unmount or new scope) or `_reset`; an
|
|
73
|
+
* invalidation drops held rows but leaves the claim.
|
|
74
|
+
*/
|
|
75
|
+
declare function claimRows(objectName: string, relField: string, parentId: string, limit: number): void;
|
|
76
|
+
/**
|
|
77
|
+
* objectui#13010 — the rail card is gone: its claim, the rows held for it and
|
|
78
|
+
* the read it started itself are dropped, so nothing outlives the record open.
|
|
79
|
+
*
|
|
80
|
+
* Contract: call it with the key a `claimRows` named, when the card unmounts
|
|
81
|
+
* or moves to a new scope (another parent or other entries). Afterwards a
|
|
82
|
+
* `fetch` of that key asks for one row again, untaken held rows are gone, and
|
|
83
|
+
* a later `fetchRows` does not join a read the card started itself. A read a
|
|
84
|
+
* badge started stays in flight for the badges that joined it.
|
|
85
|
+
*/
|
|
86
|
+
declare function releaseRows(objectName: string, relField: string, parentId: string): void;
|
|
35
87
|
/**
|
|
36
88
|
* Invalidate every cached count that involves the given object. Called by
|
|
37
89
|
* mutation paths (e.g. ObjectGrid's onBulkDelete callback, drawer save) so
|
|
@@ -70,6 +122,9 @@ export declare const RelatedCountStore: {
|
|
|
70
122
|
get: typeof getCount;
|
|
71
123
|
set: typeof setCount;
|
|
72
124
|
fetch: typeof fetchCount;
|
|
125
|
+
fetchRows: typeof fetchRows;
|
|
126
|
+
claimRows: typeof claimRows;
|
|
127
|
+
releaseRows: typeof releaseRows;
|
|
73
128
|
invalidate: typeof invalidate;
|
|
74
129
|
invalidateAll: typeof invalidateAll;
|
|
75
130
|
_reset: () => void;
|