@object-ui/react 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.
- package/CHANGELOG.md +4100 -0
- package/README.md +303 -46
- package/dist/LazyPluginLoader.d.ts +14 -7
- package/dist/LazyPluginLoader.js +14 -7
- package/dist/SchemaRenderer.d.ts +38 -7
- package/dist/SchemaRenderer.js +1603 -60
- package/dist/context/ActionContext.js +40 -1
- package/dist/context/DiscussionContext.d.ts +12 -0
- package/dist/context/DiscussionContext.js +2 -0
- package/dist/context/DrillNavigationContext.d.ts +13 -0
- package/dist/context/HostNavigationContext.d.ts +82 -0
- package/dist/context/HostNavigationContext.js +65 -0
- package/dist/context/InlineEditContext.d.ts +81 -1
- package/dist/context/InlineEditContext.js +8 -2
- package/dist/context/RecordContext.d.ts +48 -5
- package/dist/context/RecordContext.js +10 -1
- package/dist/context/RelatedRecordActionsContext.d.ts +20 -1
- package/dist/context/SchemaRendererContext.d.ts +54 -2
- package/dist/context/SchemaRendererContext.js +38 -5
- package/dist/context/ThemeContext.js +1 -1
- package/dist/context/actionRunnerTranslator.d.ts +27 -0
- package/dist/context/actionRunnerTranslator.js +37 -0
- package/dist/context/index.d.ts +13 -12
- package/dist/context/index.js +13 -12
- package/dist/element-data-source/ElementDataSourceGate.d.ts +163 -3
- package/dist/element-data-source/ElementDataSourceGate.js +407 -15
- package/dist/hooks/index.d.ts +37 -35
- package/dist/hooks/index.js +44 -35
- package/dist/hooks/useActionEngine.js +47 -4
- package/dist/hooks/useActionRunner.js +4 -0
- package/dist/hooks/useActionTextLocalizer.d.ts +21 -8
- package/dist/hooks/useActionTextLocalizer.js +33 -8
- package/dist/hooks/useCapabilityGate.js +6 -5
- package/dist/hooks/useConfigBagEvaluator.d.ts +30 -0
- package/dist/hooks/useConfigBagEvaluator.js +39 -0
- package/dist/hooks/useCrudShortcuts.js +1 -1
- package/dist/hooks/useDatasetDimensionLabels.js +1 -1
- package/dist/hooks/useDensityMode.d.ts +6 -2
- package/dist/hooks/useDensityMode.js +6 -2
- package/dist/hooks/useDiscovery.d.ts +1 -10
- package/dist/hooks/useDiscovery.js +1 -1
- package/dist/hooks/useETagCache.js +25 -4
- package/dist/hooks/useElementDataSource.d.ts +15 -2
- package/dist/hooks/useElementDataSource.js +29 -9
- package/dist/hooks/useExpression.d.ts +52 -38
- package/dist/hooks/useExpression.js +62 -42
- package/dist/hooks/useFilterScope.d.ts +22 -2
- package/dist/hooks/useFilterScope.js +29 -4
- package/dist/hooks/useGlobalUndo.d.ts +10 -0
- package/dist/hooks/useGlobalUndo.js +112 -46
- package/dist/hooks/useNavigationOverlay.d.ts +77 -12
- package/dist/hooks/useNavigationOverlay.js +119 -9
- package/dist/hooks/useOffline.js +65 -11
- package/dist/hooks/usePageAssignment.d.ts +8 -5
- package/dist/hooks/usePageAssignment.js +38 -17
- package/dist/hooks/usePageTransition.js +1 -1
- package/dist/hooks/usePageVariableActionBridge.js +2 -2
- package/dist/hooks/useRecordSearch.d.ts +34 -1
- package/dist/hooks/useRecordSearch.js +42 -7
- package/dist/hooks/useResolvedFilter.d.ts +46 -0
- package/dist/hooks/useResolvedFilter.js +130 -0
- package/dist/hooks/useSchemaPersistence.d.ts +7 -0
- package/dist/hooks/useSchemaPersistence.js +144 -5
- package/dist/hooks/useSettledSchema.d.ts +123 -0
- package/dist/hooks/useSettledSchema.js +186 -0
- package/dist/hooks/useViewData.d.ts +7 -2
- package/dist/hooks/useViewData.js +1 -1
- package/dist/hooks/useViewTransition.js +1 -1
- package/dist/index.d.ts +18 -11
- package/dist/index.js +54 -10
- package/dist/schema-input.d.ts +55 -14
- package/dist/schema-input.js +55 -12
- package/dist/utils/configBag.d.ts +68 -0
- package/dist/utils/configBag.js +70 -0
- package/dist/utils/error-message.d.ts +59 -0
- package/dist/utils/error-message.js +116 -1
- package/dist/utils/inlineAria.d.ts +61 -0
- package/dist/utils/inlineAria.js +70 -0
- package/dist/utils/nonGridRowCeiling.d.ts +44 -0
- package/dist/utils/nonGridRowCeiling.js +78 -0
- package/dist/utils/paramsBag.d.ts +28 -0
- package/dist/utils/paramsBag.js +146 -0
- package/dist/utils/propsBagDiagnostic.d.ts +196 -0
- package/dist/utils/propsBagDiagnostic.js +421 -0
- package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
- package/dist/utils/refusedDataPropDiagnostic.js +94 -0
- package/dist/utils/unevaluatedExpression.d.ts +110 -0
- package/dist/utils/unevaluatedExpression.js +257 -0
- package/dist/utils/visibilityDiagnostic.d.ts +371 -0
- package/dist/utils/visibilityDiagnostic.js +551 -0
- package/package.json +10 -10
- package/dist/hooks/useClientNotifications.d.ts +0 -47
- package/dist/hooks/useClientNotifications.js +0 -162
- package/dist/spec-bridge/SpecBridge.d.ts +0 -28
- package/dist/spec-bridge/SpecBridge.js +0 -56
- package/dist/spec-bridge/bridges/form-view.d.ts +0 -82
- package/dist/spec-bridge/bridges/form-view.js +0 -126
- package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
- package/dist/spec-bridge/bridges/list-view.js +0 -241
- package/dist/spec-bridge/index.d.ts +0 -11
- package/dist/spec-bridge/index.js +0 -10
- package/dist/spec-bridge/types.d.ts +0 -55
- package/dist/spec-bridge/types.js +0 -8
|
@@ -0,0 +1,551 @@
|
|
|
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
|
+
* Diagnostic: a node-gate predicate could not be evaluated (objectui#5454,
|
|
10
|
+
* leg 3 of the 2026-08-21 ruling; production coverage added by objectui#6038,
|
|
11
|
+
* maintainer ruling 2026-08-25 option B; the `disabled` / `disabledOn` gate
|
|
12
|
+
* added by objectui#6445).
|
|
13
|
+
*
|
|
14
|
+
* ## The defect this names
|
|
15
|
+
*
|
|
16
|
+
* `SchemaRenderer`'s visibility chain is FAIL-SOFT: `evaluateCondition` answers
|
|
17
|
+
* an unevaluable predicate with `true`. On the four negated legs (`visible` /
|
|
18
|
+
* `visibleWhen` / `visibleOn` / `visibility`) that `true` means SHOWN — so "this
|
|
19
|
+
* predicate is broken" and "this predicate said yes" reach the screen as the
|
|
20
|
+
* same pixel. An author who mistyped a root, or who wrote a `record.*` gate
|
|
21
|
+
* before objectui#5454 bound the row, saw their block render and had no signal
|
|
22
|
+
* at all that the gate had not been consulted.
|
|
23
|
+
*
|
|
24
|
+
* One of the three evaluation paths was already loud — a `{ dialect: 'cel' }`
|
|
25
|
+
* envelope routes to `evalFieldPredicate`, which warns (objectstack#5149). The
|
|
26
|
+
* bare-expression and `${…}` template paths were mute. So whether an author
|
|
27
|
+
* heard about their own typo depended on which dialect they happened to write
|
|
28
|
+
* it in, which is the arbitrariness this module removes.
|
|
29
|
+
*
|
|
30
|
+
* ## What it deliberately does NOT do
|
|
31
|
+
*
|
|
32
|
+
* It changes NO verdict. `evaluateCondition` already returned `true` for every
|
|
33
|
+
* unevaluable predicate on every one of its paths, and the caller reproduces
|
|
34
|
+
* exactly that from its catch — including on the two NON-negated legs
|
|
35
|
+
* (`hidden` / `hiddenOn`), where the same `true` means HIDE — and, since
|
|
36
|
+
* objectui#6445, on the `disabled` / `disabledOn` gate, where it means GREYED
|
|
37
|
+
* OUT. Flipping fail-soft to fail-closed is a shipped-behaviour change tracked
|
|
38
|
+
* separately upstream (objectstack#5149, appeal 1, undecided); this module is
|
|
39
|
+
* the diagnostic half only, on every gate wired to it.
|
|
40
|
+
*
|
|
41
|
+
* ## Why a separate module
|
|
42
|
+
*
|
|
43
|
+
* Same reason as `unevaluatedExpression.ts` next door: exporting non-components
|
|
44
|
+
* from the module that exports `SchemaRenderer` breaks Fast Refresh
|
|
45
|
+
* (`react-refresh/only-export-components`), and a format/emit split lets the
|
|
46
|
+
* pins assert the words a developer will actually read rather than that a spy
|
|
47
|
+
* was called — a diagnostic whose only test is the latter goes green the moment
|
|
48
|
+
* someone no-ops it.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Prefix every diagnostic line starts with. Exported so tests can match on it
|
|
52
|
+
* and so an app can filter it out of its console transport if it must.
|
|
53
|
+
*/
|
|
54
|
+
export const UNRESOLVABLE_VISIBILITY_PREFIX = '[ObjectUI] A visibility predicate could not be evaluated';
|
|
55
|
+
/**
|
|
56
|
+
* The same opening line for the ENABLEMENT gate (`disabled` / `disabledOn`,
|
|
57
|
+
* objectui#6445). A sibling constant rather than a widened one: the six
|
|
58
|
+
* visibility legs keep the bytes they ship today (objectui#6487 pinned them),
|
|
59
|
+
* and calling a `disabled` predicate a "visibility predicate" would send an
|
|
60
|
+
* author to the wrong gate on the first line - the one line a console filter
|
|
61
|
+
* and a `grep` both read.
|
|
62
|
+
*
|
|
63
|
+
* Same posture as its sibling in every other respect: same reporter, same
|
|
64
|
+
* severity, same dedupe `Set`, same key shape, same test-only reset. Only the
|
|
65
|
+
* words that would be FALSE on this gate differ.
|
|
66
|
+
*/
|
|
67
|
+
export const UNRESOLVABLE_ENABLEMENT_PREFIX = '[ObjectUI] An enablement predicate could not be evaluated';
|
|
68
|
+
/** The raw predicate as an author would recognise it, envelope or not. */
|
|
69
|
+
function predicateSourceText(raw) {
|
|
70
|
+
if (raw && typeof raw === 'object') {
|
|
71
|
+
const source = raw.source;
|
|
72
|
+
if (typeof source === 'string')
|
|
73
|
+
return source;
|
|
74
|
+
}
|
|
75
|
+
return typeof raw === 'string' ? raw : String(raw);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The closing paragraph, per tier. The ONLY part of the message that varies —
|
|
79
|
+
* everything above it (the node, the key, the source, the engine's reason, and
|
|
80
|
+
* the "gate did NOT bite" sentence) is true on every surface wired to this
|
|
81
|
+
* reporter, including the fail-open app-shell gate.
|
|
82
|
+
*
|
|
83
|
+
* Both entries end on the same sentence on purpose: whichever tier an author is
|
|
84
|
+
* on, the two things to check are the roots and the CEL syntax.
|
|
85
|
+
*
|
|
86
|
+
* Indexed WITHOUT a `??` fallback to the node tier, deliberately (AGENTS.md
|
|
87
|
+
* #0.1). A tier this table does not answer is a type violation, and quietly
|
|
88
|
+
* substituting the node tier's paragraph for it would reproduce, one caller
|
|
89
|
+
* further along, the exact defect this card fixes: confident advice about a
|
|
90
|
+
* scope the predicate was never evaluated against. The default lives on the
|
|
91
|
+
* PARAMETER, where it is a stated compatibility choice, not in the lookup.
|
|
92
|
+
*/
|
|
93
|
+
const SCOPE_TIER_ADVICE = {
|
|
94
|
+
'page-component': 'Page-component predicates bind `record` (the row on a record page),\n' +
|
|
95
|
+
'`current_user`, and page state as `page.<var>`. Check those roots and the\n' +
|
|
96
|
+
'CEL syntax.',
|
|
97
|
+
'app-shell': 'App-shell predicates bind `current_user` - also spelled `user`, `ctx.user`\n' +
|
|
98
|
+
'and `os.user` - plus `features` (the deployment flags).\n' +
|
|
99
|
+
'Neither `record` nor `page.<var>` exists at this tier.\n' +
|
|
100
|
+
'Check those roots and the CEL syntax.',
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* The two parts of the message that vary with the gate: the opening line (what
|
|
104
|
+
* KIND of predicate failed) and the consequence paragraph (what the fail-soft
|
|
105
|
+
* default DID to the node). Everything between them - the node, the key, the
|
|
106
|
+
* source, the engine's reason - and the scope advice after them are true on
|
|
107
|
+
* every gate.
|
|
108
|
+
*
|
|
109
|
+
* Indexed WITHOUT a `??` fallback, for the same reason {@link SCOPE_TIER_ADVICE}
|
|
110
|
+
* is: quietly substituting one gate's consequence for another's IS the defect
|
|
111
|
+
* being fixed here, and doing it at the lookup would only move it one caller
|
|
112
|
+
* further along. The default lives on the PARAMETER, where it is a stated
|
|
113
|
+
* compatibility choice.
|
|
114
|
+
*/
|
|
115
|
+
const GATE_KIND_COPY = {
|
|
116
|
+
visibility: {
|
|
117
|
+
prefix: UNRESOLVABLE_VISIBILITY_PREFIX,
|
|
118
|
+
consequence: 'The node was treated as its safe default, which on this surface means the\n' +
|
|
119
|
+
'gate did NOT bite - a predicate that cannot be evaluated reads on screen\n' +
|
|
120
|
+
'exactly like one that said yes.\n',
|
|
121
|
+
},
|
|
122
|
+
// The two NON-negated visibility legs (objectui#6503). SAME opening line as
|
|
123
|
+
// `visibility` above, deliberately: these ARE visibility predicates, the six
|
|
124
|
+
// legs of that chain keep the bytes objectui#6487 pinned, and an app - or a
|
|
125
|
+
// test - filtering the console by `UNRESOLVABLE_VISIBILITY_PREFIX` has to go
|
|
126
|
+
// on catching them. objectui#6038's pin that the `hidden` leg reports AT ALL
|
|
127
|
+
// reads through exactly that filter, and it must stay green: this card moves
|
|
128
|
+
// the sentence that was false for these legs, and nothing else.
|
|
129
|
+
concealment: {
|
|
130
|
+
prefix: UNRESOLVABLE_VISIBILITY_PREFIX,
|
|
131
|
+
consequence: 'The node was treated as its safe default, which on THIS leg is the one\n' +
|
|
132
|
+
'that BITES: `hidden` / `hiddenOn` are NOT negated, so that default\n' +
|
|
133
|
+
'REMOVED the node - it is not on the page at all, and an absent node is\n' +
|
|
134
|
+
'indistinguishable from metadata that meant to hide it. Nothing is broken\n' +
|
|
135
|
+
'in the renderer: the block is missing because the predicate above could\n' +
|
|
136
|
+
'not be evaluated.\n',
|
|
137
|
+
},
|
|
138
|
+
enablement: {
|
|
139
|
+
prefix: UNRESOLVABLE_ENABLEMENT_PREFIX,
|
|
140
|
+
consequence: 'The node was treated as its safe default, which on THIS gate is the one\n' +
|
|
141
|
+
'that BITES: the node renders DISABLED - on screen, greyed out, refusing\n' +
|
|
142
|
+
'input - and that is indistinguishable from a gate the author meant to\n' +
|
|
143
|
+
'close. No pixel says a predicate failed, so this line is the only thing\n' +
|
|
144
|
+
'that will ever name the one that did it.\n',
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Build the message. Separate from the emit so a test can assert the words,
|
|
149
|
+
* not merely that something was logged.
|
|
150
|
+
*
|
|
151
|
+
* `tier` defaults to `'page-component'` and `gate` to `'visibility'` so the
|
|
152
|
+
* historical five-argument call keeps printing the exact bytes it printed
|
|
153
|
+
* before (objectui#6487, objectui#6445) — the defaults are a compatibility
|
|
154
|
+
* shim for callers outside this repo, not something this repo leans on: every
|
|
155
|
+
* in-repo call site passes both explicitly, which is what makes each one a
|
|
156
|
+
* stated decision rather than an inherited one.
|
|
157
|
+
*/
|
|
158
|
+
export function formatUnresolvableVisibilityMessage(type, id, key, raw, reason, tier = 'page-component', gate = 'visibility') {
|
|
159
|
+
const node = typeof type === 'string' && type ? '"' + type + '"' : '(untyped node)';
|
|
160
|
+
const where = typeof id === 'string' && id ? ' (id: "' + id + '")' : '';
|
|
161
|
+
const copy = GATE_KIND_COPY[gate];
|
|
162
|
+
return (copy.prefix + ' - node ' + node + where + '\n' +
|
|
163
|
+
' ' + key + ': ' + JSON.stringify(predicateSourceText(raw)) + '\n' +
|
|
164
|
+
' Reason: ' + reason + '\n' +
|
|
165
|
+
copy.consequence +
|
|
166
|
+
SCOPE_TIER_ADVICE[tier]);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Reported (node type, key, predicate source) triples, so a re-render — or the
|
|
170
|
+
* post-mount `forceUpdate` that picks up lazy plugin registrations — does not
|
|
171
|
+
* repeat the line. Keyed on the predicate TEXT rather than the schema object:
|
|
172
|
+
* the same broken predicate authored once and rendered over many rows is ONE
|
|
173
|
+
* authoring bug, and an object key would report it once per row.
|
|
174
|
+
*
|
|
175
|
+
* This is the RATE LIMIT the 2026-08-25 ruling requires of the production leg,
|
|
176
|
+
* and it is why that leg can be a plain `console.warn`: the ceiling is not "one
|
|
177
|
+
* line per render" but "one line per distinct authored predicate", for the
|
|
178
|
+
* lifetime of the page. Two properties have to hold together, and a test that
|
|
179
|
+
* pins only the first cannot tell a working dedupe from one that suppresses
|
|
180
|
+
* everything — so objectui#6038 pins both: N renders of ONE faulting predicate
|
|
181
|
+
* emit exactly one line, and a SECOND distinct predicate source still emits.
|
|
182
|
+
*/
|
|
183
|
+
const _warnedVisibilityPredicates = new Set();
|
|
184
|
+
/**
|
|
185
|
+
* Reports a NODE-GATE predicate that could not be evaluated — in DEVELOPMENT
|
|
186
|
+
* AND IN PRODUCTION since objectui#6038 (maintainer ruling 2026-08-25, option
|
|
187
|
+
* B: "the silence is no longer an accepted property").
|
|
188
|
+
*
|
|
189
|
+
* The visibility chain was the first gate wired to it and is still the default
|
|
190
|
+
* {@link PredicateGateKind}; `disabled` / `disabledOn` joined it in
|
|
191
|
+
* objectui#6445, which is why the name is now narrower than the function. It is
|
|
192
|
+
* kept anyway: this symbol is exported from the package entry and called from
|
|
193
|
+
* `@object-ui/app-shell` and `@object-ui/components`, so renaming it would be a
|
|
194
|
+
* cross-package edit that changes no behaviour, on a card scoped to one file's
|
|
195
|
+
* wiring. What has to stay true is the posture the name once described — ONE
|
|
196
|
+
* reporter, ONE dedupe `Set`, ONE severity, ONE reset — and a second reporter
|
|
197
|
+
* for the second gate is exactly what that rules out.
|
|
198
|
+
*
|
|
199
|
+
* `console.warn`, not `error`: the verdict is unchanged and the page still
|
|
200
|
+
* renders, so this is a diagnostic about a predicate — not the refusal
|
|
201
|
+
* `reportUnevaluatedExpressions` emits once raw source has reached the DOM.
|
|
202
|
+
*
|
|
203
|
+
* ## `err` takes a reason, not only an `Error`
|
|
204
|
+
*
|
|
205
|
+
* The dev caller catches a throw and passes the `Error`; the production caller
|
|
206
|
+
* is handed the evaluator's own reason STRING through `EvaluationOptions.onFault`
|
|
207
|
+
* (no throw is raised there, because raising one would cost a second
|
|
208
|
+
* evaluation). `String(err)` already covered that shape, so both callers reach
|
|
209
|
+
* the same `Reason:` text and the same dedupe entry — which is what makes "dev
|
|
210
|
+
* and production print the identical line" true rather than approximately true.
|
|
211
|
+
*
|
|
212
|
+
* ## `tier` is NOT in the dedupe key, and that is not an oversight
|
|
213
|
+
*
|
|
214
|
+
* The key stays `(type, key, predicate source)` — the rate limit objectui#6038
|
|
215
|
+
* pinned in both directions. Adding `tier` could only ever LOOSEN it, and it
|
|
216
|
+
* would take a `type` shared across two tiers to loosen anything: the app-shell
|
|
217
|
+
* site's `type` is the constant `'app-shell:visible'`, which no node tier can
|
|
218
|
+
* produce, and the two `'page-component'` callers are the same tier by
|
|
219
|
+
* definition. So the tier is free of the key by measurement, not by assumption.
|
|
220
|
+
*
|
|
221
|
+
* ## Neither is `gate`, and for a stronger reason (objectui#6445)
|
|
222
|
+
*
|
|
223
|
+
* `gate` is a FUNCTION of `key`, which is already in the dedupe key: the
|
|
224
|
+
* enablement gate is exactly `disabled` / `disabledOn` and the visibility gate
|
|
225
|
+
* is exactly the six legs of the visibility chain, two disjoint sets. So adding
|
|
226
|
+
* it could not separate two entries that `key` does not already separate, and
|
|
227
|
+
* the cross-gate direction is pinned rather than argued: the SAME predicate
|
|
228
|
+
* source authored on `disabled` and on `visibleWhen` produces TWO lines, not
|
|
229
|
+
* one - the new reporting site cannot be swallowed by a visibility entry, and
|
|
230
|
+
* cannot swallow one.
|
|
231
|
+
*/
|
|
232
|
+
export function reportUnresolvableVisibilityPredicate(type, id, key, raw, err, tier = 'page-component', gate = 'visibility') {
|
|
233
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
234
|
+
const dedupeKey = JSON.stringify([type, key, predicateSourceText(raw)]);
|
|
235
|
+
if (_warnedVisibilityPredicates.has(dedupeKey))
|
|
236
|
+
return;
|
|
237
|
+
_warnedVisibilityPredicates.add(dedupeKey);
|
|
238
|
+
console.warn(formatUnresolvableVisibilityMessage(type, id, key, raw, reason, tier, gate));
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Test-only reset for the dedupe above — for BOTH legs, which is the point of
|
|
242
|
+
* their sharing one `Set`. The `Set` is module state: without this, the second
|
|
243
|
+
* test to assert the same warning reads the first test's dedupe entry and sees
|
|
244
|
+
* silence — a green run that checked nothing.
|
|
245
|
+
*/
|
|
246
|
+
export function __resetVisibilityPredicateWarnings() {
|
|
247
|
+
_warnedVisibilityPredicates.clear();
|
|
248
|
+
}
|
|
249
|
+
/* -------------------------------------------------------------------------- *
|
|
250
|
+
* objectui#5687 — the NON-throwing half of the same silence
|
|
251
|
+
* -------------------------------------------------------------------------- */
|
|
252
|
+
/**
|
|
253
|
+
* Prefix for the objectui#5687 leg. A SIBLING of the constant above, not a
|
|
254
|
+
* reuse of it, and the difference is deliberate.
|
|
255
|
+
*
|
|
256
|
+
* The maintainer ruling (2026-08-22, option A) says this path "emits the
|
|
257
|
+
* dev-only unresolvable-predicate report". What is load-bearing there — and
|
|
258
|
+
* what the dispatch restated — is the reporter's POSTURE: same module, same
|
|
259
|
+
* severity (`console.warn`), same dedupe key shape, same dedupe Set, same
|
|
260
|
+
* gate at the same call site, same test-only reset. All of that is shared
|
|
261
|
+
* below.
|
|
262
|
+
*
|
|
263
|
+
* ⚠️ The two legs stopped sharing that gate's VALUE in objectui#6038, and only
|
|
264
|
+
* that. The unresolvable leg now reports in production as well, because the
|
|
265
|
+
* 2026-08-25 ruling retired its silence; THIS leg stays dev-only under its own
|
|
266
|
+
* 2026-08-22 ruling, and the difference is not an oversight. A fault is a
|
|
267
|
+
* predicate that could not be evaluated, and shipping a live gate that has
|
|
268
|
+
* stopped biting is the class-1 defect production has to be able to see. This
|
|
269
|
+
* leg reports something else: a predicate that evaluated perfectly, against the
|
|
270
|
+
* wrong object. Its trigger is a LEXICAL scan of the predicate source with a
|
|
271
|
+
* stated false-positive residue (the deliberate-absence idioms below), which is
|
|
272
|
+
* a cost worth paying for an author at their keyboard and not for every user of
|
|
273
|
+
* every production page. The first LINE is not reused, because on this path it would
|
|
274
|
+
* state something false: the predicate did not fail to evaluate. It evaluated
|
|
275
|
+
* perfectly, against the wrong object, and produced a constant. Telling an
|
|
276
|
+
* author "could not be evaluated" would send them hunting for a syntax error
|
|
277
|
+
* that is not there.
|
|
278
|
+
*/
|
|
279
|
+
export const ADAPTER_ONLY_DATA_PREDICATE_PREFIX = '[ObjectUI] A visibility predicate read `data.*` that the bound `data` does not answer';
|
|
280
|
+
/**
|
|
281
|
+
* The `disabled` / `disabledOn` sibling of the prefix above (objectui#6504,
|
|
282
|
+
* maintainer ruling 2026-08-27 option A). Same reason
|
|
283
|
+
* {@link UNRESOLVABLE_ENABLEMENT_PREFIX} is a sibling of
|
|
284
|
+
* {@link UNRESOLVABLE_VISIBILITY_PREFIX} rather than a reuse: calling a
|
|
285
|
+
* `disabled` predicate "a visibility predicate" would send an author to the
|
|
286
|
+
* wrong gate on the first line — the one line a console filter and a `grep`
|
|
287
|
+
* both read.
|
|
288
|
+
*/
|
|
289
|
+
export const ADAPTER_ONLY_ENABLEMENT_PREDICATE_PREFIX = '[ObjectUI] An enablement predicate read `data.*` that the bound `data` does not answer';
|
|
290
|
+
/**
|
|
291
|
+
* The two parts of the message that vary with the gate, mirroring
|
|
292
|
+
* {@link GATE_KIND_COPY}'s machinery (#6445) for this SIBLING diagnostic —
|
|
293
|
+
* same discipline (indexed without a `??` fallback; the default lives on the
|
|
294
|
+
* parameter), a separate table because the message TEMPLATE differs (this one
|
|
295
|
+
* carries an "Unanswered by the bound `data`:" line and no {@link SCOPE_TIER_ADVICE}
|
|
296
|
+
* tail; see {@link formatAdapterOnlyDataMessage}).
|
|
297
|
+
*
|
|
298
|
+
* ⛔⛔ BINDING INHERITANCE CLAUSE (2026-08-27 ruling, objectui#6504, verbatim
|
|
299
|
+
* from the card thread): "the extension inherits #5687's dissolution note
|
|
300
|
+
* verbatim — when #5330's deprecation window closes, both legs dissolve
|
|
301
|
+
* together; the new copy entry must carry the same pointer so the teardown
|
|
302
|
+
* finds it." The pointer, carried from this module's own docblock (see
|
|
303
|
+
* {@link unresolvedDataPaths}'s "why this discriminator" section): objectui#5687
|
|
304
|
+
* is "a diagnostic on a card that dissolves when objectui#5330's deprecation
|
|
305
|
+
* window closes." BOTH entries below dissolve on that same clock, together —
|
|
306
|
+
* this is not an independently-scheduled diagnostic. When that window closes,
|
|
307
|
+
* delete this table, {@link ADAPTER_ONLY_ENABLEMENT_PREDICATE_PREFIX},
|
|
308
|
+
* {@link ADAPTER_ONLY_DATA_PREDICATE_PREFIX}, {@link unresolvedDataPaths},
|
|
309
|
+
* {@link formatAdapterOnlyDataMessage} and {@link reportAdapterOnlyDataPredicate}
|
|
310
|
+
* in this file, and BOTH call sites in `SchemaRenderer.tsx`
|
|
311
|
+
* (`evaluateVisibilityPredicate` and `evaluateEnablementPredicate`), in the
|
|
312
|
+
* same stroke.
|
|
313
|
+
*/
|
|
314
|
+
const ADAPTER_ONLY_GATE_COPY = {
|
|
315
|
+
visibility: {
|
|
316
|
+
prefix: ADAPTER_ONLY_DATA_PREDICATE_PREFIX,
|
|
317
|
+
consequence: 'At the node tier `data` is whatever the HOST published under that name\n' +
|
|
318
|
+
'in the ambient predicate scope, and it is NOT the row. Since\n' +
|
|
319
|
+
'objectui#9308 it is not the data-source adapter either - the renderer\n' +
|
|
320
|
+
'binds no `data` of its own. The reads above are undefined on it, so\n' +
|
|
321
|
+
'this predicate is a CONSTANT: it does not\n' +
|
|
322
|
+
'depend on the row at all, and on this surface a constant `false` hides the\n' +
|
|
323
|
+
'node on every row while looking exactly like a gate that said no.\n' +
|
|
324
|
+
'Write the row as `record.*` (e.g. `record.status`), which page-component\n' +
|
|
325
|
+
'predicates bind alongside `current_user` and page state as `page.<var>`.\n' +
|
|
326
|
+
'`data.*` as a spelling for the row is deprecated (objectui#5330) and was\n' +
|
|
327
|
+
'never bound to the row on this tier.',
|
|
328
|
+
},
|
|
329
|
+
// The constant-`true` direction, not a reuse of the sentence above: on THIS
|
|
330
|
+
// gate the fail-soft default that BITES is `true` (a control that greys
|
|
331
|
+
// out), the mirror image of the negated visibility legs where it is
|
|
332
|
+
// `false` (a node that hides). Written to describe that direction, since it
|
|
333
|
+
// is the one that leaves nothing on screen for an author to notice —
|
|
334
|
+
// "greyed out" looks exactly like a gate that closed on purpose, the same
|
|
335
|
+
// way "not on screen" looks exactly like a gate that hid on purpose.
|
|
336
|
+
enablement: {
|
|
337
|
+
prefix: ADAPTER_ONLY_ENABLEMENT_PREDICATE_PREFIX,
|
|
338
|
+
consequence: 'At the node tier `data` is whatever the HOST published under that name\n' +
|
|
339
|
+
'in the ambient predicate scope, and it is NOT the row. Since\n' +
|
|
340
|
+
'objectui#9308 it is not the data-source adapter either - the renderer\n' +
|
|
341
|
+
'binds no `data` of its own. The reads above are undefined on it, so\n' +
|
|
342
|
+
'this predicate is a CONSTANT: it does not\n' +
|
|
343
|
+
'depend on the row at all, and on THIS gate the dangerous polarity is a\n' +
|
|
344
|
+
'constant `true`: the node renders DISABLED - on screen, greyed out,\n' +
|
|
345
|
+
'refusing input - on every row, in every build, while looking exactly\n' +
|
|
346
|
+
'like a gate that said no. No pixel says the predicate is a constant, so\n' +
|
|
347
|
+
'this line is the only thing that will ever name it.\n' +
|
|
348
|
+
'Write the row as `record.*` (e.g. `record.status`), which page-component\n' +
|
|
349
|
+
'predicates bind alongside `current_user` and page state as `page.<var>`.\n' +
|
|
350
|
+
'`data.*` as a spelling for the row is deprecated (objectui#5330) and was\n' +
|
|
351
|
+
'never bound to the row on this tier.',
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* Strip string literals before scanning for `data.*` reads.
|
|
356
|
+
*
|
|
357
|
+
* The scan below is LEXICAL, not a parse — it reads the predicate's source
|
|
358
|
+
* text. Without this step a predicate that merely quotes the characters
|
|
359
|
+
* (`note == 'data.status'`) would be reported for a read it never performs.
|
|
360
|
+
* That class is not hypothetical in this repo: `examples/schema-catalog`'s
|
|
361
|
+
* `components-complex-scroll-area/code-preview.json` carries `data.features`
|
|
362
|
+
* inside a JS snippet in a `content` string.
|
|
363
|
+
*
|
|
364
|
+
* Replaced with an EMPTY literal of the same quote style rather than deleted,
|
|
365
|
+
* so neighbouring tokens cannot be fused into a new false match.
|
|
366
|
+
*/
|
|
367
|
+
function stripStringLiterals(source) {
|
|
368
|
+
return source.replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, "''");
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* `data.<path>` reads, matched only where `data` is a ROOT identifier.
|
|
372
|
+
*
|
|
373
|
+
* The leading group is what keeps this from being the loose sweep the card's
|
|
374
|
+
* own cross-reference warns about: `metadata.status` (a longer identifier that
|
|
375
|
+
* ends in `data`) and `record.data.status` (`data` as a MEMBER, which is the
|
|
376
|
+
* row's own field, not this evaluator's root) must not match. Both are pinned.
|
|
377
|
+
* A capture group is used rather than a lookbehind for target-browser reach.
|
|
378
|
+
*/
|
|
379
|
+
const DATA_ROOT_PATH_RE = /(^|[^A-Za-z0-9_$.])data\.([A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)*)/g;
|
|
380
|
+
/**
|
|
381
|
+
* The `data.*` reads in `source` that are `undefined` on the object `data` is
|
|
382
|
+
* actually bound to — i.e. the ones that make the comparison a constant.
|
|
383
|
+
*
|
|
384
|
+
* ## Why THIS discriminator, measured against the three alternatives
|
|
385
|
+
*
|
|
386
|
+
* The ruling's trigger is not "references `data.`" — a genuine adapter read has
|
|
387
|
+
* to stay silent, and that half is what makes the loud half mean anything.
|
|
388
|
+
* Measured on `packages/core`'s built evaluator, against the four candidate
|
|
389
|
+
* definitions:
|
|
390
|
+
*
|
|
391
|
+
* * "references `data.` at all" — fires on `data.total > 0` against an
|
|
392
|
+
* adapter that HAS `total`. Refused by the ruling in as many words.
|
|
393
|
+
* * "the whole predicate is constant-false" — measured: `data.total > 100`
|
|
394
|
+
* against `{ total: 99 }` returns `false`, and so does a correct
|
|
395
|
+
* `record.status == 'draft'` against a row whose status is `open`. The
|
|
396
|
+
* definition cannot tell a gate that correctly says NO from a gate that
|
|
397
|
+
* never asked. It would report every hiding gate in the repo.
|
|
398
|
+
* * "the comparison OPERAND is undefined" — the most precise reading, and it
|
|
399
|
+
* needs the expression engine to expose its operands. That is a change to
|
|
400
|
+
* `@object-ui/core`'s evaluator for a diagnostic on a card that dissolves
|
|
401
|
+
* when objectui#5330's deprecation window closes — BOTH legs together,
|
|
402
|
+
* the enablement one objectui#6504 added included; see
|
|
403
|
+
* {@link ADAPTER_ONLY_GATE_COPY}'s binding inheritance clause.
|
|
404
|
+
* * "a `data.*` read the bound object does not answer" — this one. On the
|
|
405
|
+
* card's repro (`data.status` against the adapter) it fires; on
|
|
406
|
+
* `${data.total}` against `{ total: 99 }` it does not; and it is
|
|
407
|
+
* STRUCTURALLY incapable of firing on the `record.*` bucket
|
|
408
|
+
* (objectui#5454), which is the adjacent card with a different correct fix.
|
|
409
|
+
*
|
|
410
|
+
* Residue, stated rather than hidden: deliberate absence idioms
|
|
411
|
+
* (`data.status == null`, `!data.status`) are reported. They are not really
|
|
412
|
+
* false positives at this tier — an adapter that has no `status` makes those
|
|
413
|
+
* constants too, just constant-TRUE instead of constant-false — but they are
|
|
414
|
+
* the shapes an author could have meant.
|
|
415
|
+
*
|
|
416
|
+
* The walk mirrors `useDataScope`'s own `path.split('.').reduce(…)` resolution
|
|
417
|
+
* of a path, so "undefined here" means what it means everywhere else this repo
|
|
418
|
+
* resolves a path against a scope.
|
|
419
|
+
*
|
|
420
|
+
* ⭐ WHAT `boundData` IS, since objectui#9308. It used to be the injected
|
|
421
|
+
* ADAPTER (`SchemaRendererContext.dataSource`), because the renderer bound that
|
|
422
|
+
* object as the `data` root. It no longer binds one: `boundData` is now the
|
|
423
|
+
* `data` the HOST published in the ambient predicate scope, i.e. the exact
|
|
424
|
+
* object the evaluator resolved `data.*` against. Passing the adapter after
|
|
425
|
+
* that ruling would have inverted this diagnostic — silent for the hosts whose
|
|
426
|
+
* predicates actually broke (their bag still answers), and loud for hosts whose
|
|
427
|
+
* scope-channel `data` answers perfectly.
|
|
428
|
+
*
|
|
429
|
+
* ⚠️ Reachability, stated rather than assumed: when the host publishes NO
|
|
430
|
+
* `data` root, a `data.*` predicate does not evaluate cleanly at all — it
|
|
431
|
+
* throws `data is not defined`, which is objectui#5454's leg, reported by
|
|
432
|
+
* {@link reportUnresolvableVisibilityPredicate} with a message that is true for
|
|
433
|
+
* it. Both call sites of this function sit on the NON-throwing branch, so this
|
|
434
|
+
* leg is reached only for a host that published a `data` root which does not
|
|
435
|
+
* answer the read. That is the population it can still speak for, and the one
|
|
436
|
+
* it now speaks about accurately.
|
|
437
|
+
*/
|
|
438
|
+
function unresolvedDataPaths(source, boundData) {
|
|
439
|
+
const scannable = stripStringLiterals(source);
|
|
440
|
+
const out = [];
|
|
441
|
+
DATA_ROOT_PATH_RE.lastIndex = 0;
|
|
442
|
+
let match;
|
|
443
|
+
while ((match = DATA_ROOT_PATH_RE.exec(scannable)) !== null) {
|
|
444
|
+
const path = match[2];
|
|
445
|
+
let cursor = boundData;
|
|
446
|
+
try {
|
|
447
|
+
for (const segment of path.split('.')) {
|
|
448
|
+
if (cursor === null || cursor === undefined) {
|
|
449
|
+
cursor = undefined;
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
cursor = cursor[segment];
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
// A throwing getter on the adapter is the OTHER reporter's case: the
|
|
457
|
+
// evaluator would have thrown too. Stay silent rather than guess.
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
const spelling = 'data.' + path;
|
|
461
|
+
if (cursor === undefined && out.indexOf(spelling) === -1)
|
|
462
|
+
out.push(spelling);
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Built separately from the emit, for the same reason as the message above.
|
|
468
|
+
*
|
|
469
|
+
* `gate` defaults to `'visibility'` so the historical four-argument call
|
|
470
|
+
* (objectui#5687) keeps printing the exact bytes it printed before —
|
|
471
|
+
* objectui#6504's own call site in `evaluateVisibilityPredicate`
|
|
472
|
+
* (`SchemaRenderer.tsx`) is one of those unchanged callers, passing no `gate`
|
|
473
|
+
* at all. `evaluateEnablementPredicate` is the one caller that states
|
|
474
|
+
* `'enablement'` explicitly, matching {@link formatUnresolvableVisibilityMessage}'s
|
|
475
|
+
* own compatibility-default pattern.
|
|
476
|
+
*/
|
|
477
|
+
export function formatAdapterOnlyDataMessage(type, id, key, raw, unresolved, gate = 'visibility') {
|
|
478
|
+
const node = typeof type === 'string' && type ? '"' + type + '"' : '(untyped node)';
|
|
479
|
+
const where = typeof id === 'string' && id ? ' (id: "' + id + '")' : '';
|
|
480
|
+
const copy = ADAPTER_ONLY_GATE_COPY[gate];
|
|
481
|
+
return (copy.prefix + ' - node ' + node + where + '\n' +
|
|
482
|
+
' ' + key + ': ' + JSON.stringify(predicateSourceText(raw)) + '\n' +
|
|
483
|
+
' Unanswered by the bound `data`: ' + unresolved.join(', ') + '\n' +
|
|
484
|
+
copy.consequence);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Dev-build diagnostic for a node-gate predicate that reads `data.*` and gets
|
|
488
|
+
* `undefined` back from the adapter (objectui#5687, maintainer ruling
|
|
489
|
+
* 2026-08-22 option A).
|
|
490
|
+
*
|
|
491
|
+
* ## It changes NO verdict, and it is not allowed to
|
|
492
|
+
*
|
|
493
|
+
* The ruling is explicit that the node tier keeps its documented
|
|
494
|
+
* `data` = adapter semantics: no verdict change, no interpolation change. This
|
|
495
|
+
* function returns `void`, is called for its console output only, and the
|
|
496
|
+
* caller passes the evaluator's answer through untouched. The constant-false
|
|
497
|
+
* still hides the block; the author now hears about it.
|
|
498
|
+
*
|
|
499
|
+
* ## Why it lives beside `reportUnresolvableVisibilityPredicate`
|
|
500
|
+
*
|
|
501
|
+
* They are two halves of ONE silence. objectui#5454 made the THROWING paths
|
|
502
|
+
* loud; measured on this base, a `{ dialect: 'cel' }` envelope reading
|
|
503
|
+
* `data.status` already throws and is already reported by that function. The
|
|
504
|
+
* bare-string and `${…}` template dialects do not throw for the same
|
|
505
|
+
* predicate — they resolve `undefined == 'draft'` to a clean `false` — so the
|
|
506
|
+
* author heard about the identical authoring mistake only if they happened to
|
|
507
|
+
* write it in the CEL dialect. That is the same dialect-dependent arbitrariness
|
|
508
|
+
* objectui#5454 existed to remove, one path further along.
|
|
509
|
+
*
|
|
510
|
+
* Sharing the module means sharing the LIFECYCLE, which is the part that has to
|
|
511
|
+
* match: one dedupe Set, one reset, one severity. (One gate, too, until
|
|
512
|
+
* objectui#6038 — see the prefix constant above for why only the sibling leg
|
|
513
|
+
* crossed into production.) The dedupe
|
|
514
|
+
* key is tagged with this leg's name so the two diagnostics cannot silence each
|
|
515
|
+
* other for the same (type, key, source) triple — they are different faults,
|
|
516
|
+
* and a node that faults one way is not evidence about the other.
|
|
517
|
+
*
|
|
518
|
+
* ## `gate` (objectui#6504) — NOT in the dedupe key, for the reason
|
|
519
|
+
* {@link reportUnresolvableVisibilityPredicate}'s own docblock gives for the
|
|
520
|
+
* same omission
|
|
521
|
+
*
|
|
522
|
+
* `gate` is a FUNCTION of `key`: the enablement gate is exactly `disabled` /
|
|
523
|
+
* `disabledOn` and the visibility gate this leg is called for is the six-leg
|
|
524
|
+
* chain, two disjoint sets. Adding it to the dedupe key could not separate two
|
|
525
|
+
* entries `key` does not already separate — and the cross-gate direction is
|
|
526
|
+
* the one that would matter if it could: the SAME predicate source authored on
|
|
527
|
+
* `disabled` and on (say) `visibleWhen` must produce TWO lines, which it does
|
|
528
|
+
* today because `key` differs, with or without `gate` in the tuple.
|
|
529
|
+
*
|
|
530
|
+
* Defaults to `'visibility'` — the objectui#5687 call site
|
|
531
|
+
* (`evaluateVisibilityPredicate`) is unchanged by this card and still calls
|
|
532
|
+
* this function with five arguments.
|
|
533
|
+
*/
|
|
534
|
+
export function reportAdapterOnlyDataPredicate(type, id, key, raw, boundData, gate = 'visibility') {
|
|
535
|
+
const source = predicateSourceText(raw);
|
|
536
|
+
// Fast reject: the overwhelming majority of predicates never mention `data.`
|
|
537
|
+
// at all, and this runs for every visibility predicate of every node in dev.
|
|
538
|
+
if (source.indexOf('data.') === -1)
|
|
539
|
+
return;
|
|
540
|
+
const unresolved = unresolvedDataPaths(source, boundData);
|
|
541
|
+
// Every `data.*` read the adapter answers -> a genuine adapter read.
|
|
542
|
+
// SILENT. This is the half of the ruling that makes the other half mean
|
|
543
|
+
// something.
|
|
544
|
+
if (unresolved.length === 0)
|
|
545
|
+
return;
|
|
546
|
+
const dedupeKey = JSON.stringify(['adapter-only-data', type, key, source]);
|
|
547
|
+
if (_warnedVisibilityPredicates.has(dedupeKey))
|
|
548
|
+
return;
|
|
549
|
+
_warnedVisibilityPredicates.add(dedupeKey);
|
|
550
|
+
console.warn(formatAdapterOnlyDataMessage(type, id, key, raw, unresolved, gate));
|
|
551
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/react",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "React bindings and SchemaRenderer component for Object UI",
|
|
@@ -24,12 +24,11 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@objectstack/spec": "^17.
|
|
28
|
-
"
|
|
29
|
-
"@object-ui/
|
|
30
|
-
"@object-ui/
|
|
31
|
-
"@object-ui/i18n": "17.
|
|
32
|
-
"@object-ui/types": "17.5.0"
|
|
27
|
+
"@objectstack/spec": "^17.1.0",
|
|
28
|
+
"@object-ui/data-objectstack": "17.7.0",
|
|
29
|
+
"@object-ui/types": "17.7.0",
|
|
30
|
+
"@object-ui/core": "17.7.0",
|
|
31
|
+
"@object-ui/i18n": "17.7.0"
|
|
33
32
|
},
|
|
34
33
|
"peerDependencies": {
|
|
35
34
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -38,7 +37,8 @@
|
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@types/react": "19.2.18",
|
|
40
39
|
"@types/react-dom": "19.2.4",
|
|
41
|
-
"typescript": "^6.0.3"
|
|
40
|
+
"typescript": "^6.0.3",
|
|
41
|
+
"@object-ui/test-support": "17.4.0"
|
|
42
42
|
},
|
|
43
43
|
"keywords": [
|
|
44
44
|
"objectui",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"LICENSE"
|
|
64
64
|
],
|
|
65
65
|
"scripts": {
|
|
66
|
-
"build": "tsc",
|
|
67
|
-
"test": "vitest run",
|
|
66
|
+
"build": "tsc && node ../../scripts/check-dist-completeness.mjs",
|
|
67
|
+
"test": "vitest run --root ../.. packages/react/",
|
|
68
68
|
"type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
69
69
|
"lint": "eslint ."
|
|
70
70
|
}
|
|
@@ -1,47 +0,0 @@
|
|
|
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 interface UseClientNotificationsOptions {
|
|
9
|
-
/** ObjectStack client instance (optional, from context if available) */
|
|
10
|
-
client?: any;
|
|
11
|
-
/** Whether to auto-fetch notifications on mount */
|
|
12
|
-
autoFetch?: boolean;
|
|
13
|
-
/** Polling interval in ms (0 to disable) */
|
|
14
|
-
pollInterval?: number;
|
|
15
|
-
}
|
|
16
|
-
export interface UseClientNotificationsResult {
|
|
17
|
-
/** Fetch notifications from server */
|
|
18
|
-
fetchNotifications: () => Promise<void>;
|
|
19
|
-
/** Mark a notification as read on the server */
|
|
20
|
-
markAsRead: (id: string) => Promise<void>;
|
|
21
|
-
/** Loading state */
|
|
22
|
-
loading: boolean;
|
|
23
|
-
/** Error state */
|
|
24
|
-
error: Error | null;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Bridge between the `@objectstack/client` notifications API and the
|
|
28
|
-
* local {@link NotificationContext}.
|
|
29
|
-
*
|
|
30
|
-
* The hook resolves the ObjectStack client either from the explicit
|
|
31
|
-
* `client` option or from the `SchemaRendererContext` dataSource, then
|
|
32
|
-
* delegates to `client.notifications.*` methods. Fetched notifications
|
|
33
|
-
* are fed into the local provider via `useNotifications().notify()`.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```tsx
|
|
37
|
-
* function NotificationBell() {
|
|
38
|
-
* const { fetchNotifications, loading } = useClientNotifications({
|
|
39
|
-
* autoFetch: true,
|
|
40
|
-
* pollInterval: 30_000,
|
|
41
|
-
* });
|
|
42
|
-
*
|
|
43
|
-
* return <Button onClick={fetchNotifications} disabled={loading}>🔔</Button>;
|
|
44
|
-
* }
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export declare function useClientNotifications(options?: UseClientNotificationsOptions): UseClientNotificationsResult;
|