@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.
Files changed (103) hide show
  1. package/CHANGELOG.md +4100 -0
  2. package/README.md +303 -46
  3. package/dist/LazyPluginLoader.d.ts +14 -7
  4. package/dist/LazyPluginLoader.js +14 -7
  5. package/dist/SchemaRenderer.d.ts +38 -7
  6. package/dist/SchemaRenderer.js +1603 -60
  7. package/dist/context/ActionContext.js +40 -1
  8. package/dist/context/DiscussionContext.d.ts +12 -0
  9. package/dist/context/DiscussionContext.js +2 -0
  10. package/dist/context/DrillNavigationContext.d.ts +13 -0
  11. package/dist/context/HostNavigationContext.d.ts +82 -0
  12. package/dist/context/HostNavigationContext.js +65 -0
  13. package/dist/context/InlineEditContext.d.ts +81 -1
  14. package/dist/context/InlineEditContext.js +8 -2
  15. package/dist/context/RecordContext.d.ts +48 -5
  16. package/dist/context/RecordContext.js +10 -1
  17. package/dist/context/RelatedRecordActionsContext.d.ts +20 -1
  18. package/dist/context/SchemaRendererContext.d.ts +54 -2
  19. package/dist/context/SchemaRendererContext.js +38 -5
  20. package/dist/context/ThemeContext.js +1 -1
  21. package/dist/context/actionRunnerTranslator.d.ts +27 -0
  22. package/dist/context/actionRunnerTranslator.js +37 -0
  23. package/dist/context/index.d.ts +13 -12
  24. package/dist/context/index.js +13 -12
  25. package/dist/element-data-source/ElementDataSourceGate.d.ts +163 -3
  26. package/dist/element-data-source/ElementDataSourceGate.js +407 -15
  27. package/dist/hooks/index.d.ts +37 -35
  28. package/dist/hooks/index.js +44 -35
  29. package/dist/hooks/useActionEngine.js +47 -4
  30. package/dist/hooks/useActionRunner.js +4 -0
  31. package/dist/hooks/useActionTextLocalizer.d.ts +21 -8
  32. package/dist/hooks/useActionTextLocalizer.js +33 -8
  33. package/dist/hooks/useCapabilityGate.js +6 -5
  34. package/dist/hooks/useConfigBagEvaluator.d.ts +30 -0
  35. package/dist/hooks/useConfigBagEvaluator.js +39 -0
  36. package/dist/hooks/useCrudShortcuts.js +1 -1
  37. package/dist/hooks/useDatasetDimensionLabels.js +1 -1
  38. package/dist/hooks/useDensityMode.d.ts +6 -2
  39. package/dist/hooks/useDensityMode.js +6 -2
  40. package/dist/hooks/useDiscovery.d.ts +1 -10
  41. package/dist/hooks/useDiscovery.js +1 -1
  42. package/dist/hooks/useETagCache.js +25 -4
  43. package/dist/hooks/useElementDataSource.d.ts +15 -2
  44. package/dist/hooks/useElementDataSource.js +29 -9
  45. package/dist/hooks/useExpression.d.ts +52 -38
  46. package/dist/hooks/useExpression.js +62 -42
  47. package/dist/hooks/useFilterScope.d.ts +22 -2
  48. package/dist/hooks/useFilterScope.js +29 -4
  49. package/dist/hooks/useGlobalUndo.d.ts +10 -0
  50. package/dist/hooks/useGlobalUndo.js +112 -46
  51. package/dist/hooks/useNavigationOverlay.d.ts +77 -12
  52. package/dist/hooks/useNavigationOverlay.js +119 -9
  53. package/dist/hooks/useOffline.js +65 -11
  54. package/dist/hooks/usePageAssignment.d.ts +8 -5
  55. package/dist/hooks/usePageAssignment.js +38 -17
  56. package/dist/hooks/usePageTransition.js +1 -1
  57. package/dist/hooks/usePageVariableActionBridge.js +2 -2
  58. package/dist/hooks/useRecordSearch.d.ts +34 -1
  59. package/dist/hooks/useRecordSearch.js +42 -7
  60. package/dist/hooks/useResolvedFilter.d.ts +46 -0
  61. package/dist/hooks/useResolvedFilter.js +130 -0
  62. package/dist/hooks/useSchemaPersistence.d.ts +7 -0
  63. package/dist/hooks/useSchemaPersistence.js +144 -5
  64. package/dist/hooks/useSettledSchema.d.ts +123 -0
  65. package/dist/hooks/useSettledSchema.js +186 -0
  66. package/dist/hooks/useViewData.d.ts +7 -2
  67. package/dist/hooks/useViewData.js +1 -1
  68. package/dist/hooks/useViewTransition.js +1 -1
  69. package/dist/index.d.ts +18 -11
  70. package/dist/index.js +54 -10
  71. package/dist/schema-input.d.ts +55 -14
  72. package/dist/schema-input.js +55 -12
  73. package/dist/utils/configBag.d.ts +68 -0
  74. package/dist/utils/configBag.js +70 -0
  75. package/dist/utils/error-message.d.ts +59 -0
  76. package/dist/utils/error-message.js +116 -1
  77. package/dist/utils/inlineAria.d.ts +61 -0
  78. package/dist/utils/inlineAria.js +70 -0
  79. package/dist/utils/nonGridRowCeiling.d.ts +44 -0
  80. package/dist/utils/nonGridRowCeiling.js +78 -0
  81. package/dist/utils/paramsBag.d.ts +28 -0
  82. package/dist/utils/paramsBag.js +146 -0
  83. package/dist/utils/propsBagDiagnostic.d.ts +196 -0
  84. package/dist/utils/propsBagDiagnostic.js +421 -0
  85. package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
  86. package/dist/utils/refusedDataPropDiagnostic.js +94 -0
  87. package/dist/utils/unevaluatedExpression.d.ts +110 -0
  88. package/dist/utils/unevaluatedExpression.js +257 -0
  89. package/dist/utils/visibilityDiagnostic.d.ts +371 -0
  90. package/dist/utils/visibilityDiagnostic.js +551 -0
  91. package/package.json +10 -10
  92. package/dist/hooks/useClientNotifications.d.ts +0 -47
  93. package/dist/hooks/useClientNotifications.js +0 -162
  94. package/dist/spec-bridge/SpecBridge.d.ts +0 -28
  95. package/dist/spec-bridge/SpecBridge.js +0 -56
  96. package/dist/spec-bridge/bridges/form-view.d.ts +0 -82
  97. package/dist/spec-bridge/bridges/form-view.js +0 -126
  98. package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
  99. package/dist/spec-bridge/bridges/list-view.js +0 -241
  100. package/dist/spec-bridge/index.d.ts +0 -11
  101. package/dist/spec-bridge/index.js +0 -10
  102. package/dist/spec-bridge/types.d.ts +0 -55
  103. package/dist/spec-bridge/types.js +0 -8
@@ -0,0 +1,186 @@
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 { useEffect, useState } from 'react';
9
+ /**
10
+ * Is this refetch's answer the SAME answer, by value, as the one already
11
+ * published?
12
+ *
13
+ * Used for exactly one decision — whether a settle republishes or keeps the
14
+ * object consumers already hold — and its failure direction is chosen for
15
+ * that decision. A false NEGATIVE (two encodings of the same metadata that
16
+ * serialise differently, e.g. with the keys in another order) republishes,
17
+ * which is precisely what this hook did for every answer before
18
+ * objectui#10106: no consumer can be worse off than it already was. A false
19
+ * POSITIVE would withhold a genuinely new definition from consumers, so the
20
+ * comparison is deliberately a whole-payload one and never a sampled or
21
+ * shallow test.
22
+ *
23
+ * ⚠️ An answer JSON cannot express — a cycle, a `BigInt` — makes
24
+ * `JSON.stringify` throw; that is answered as "not equal" rather than allowed
25
+ * to escape into a state updater, which lands on the safe side above.
26
+ */
27
+ function isEqualPayload(a, b) {
28
+ if (Object.is(a, b))
29
+ return true;
30
+ try {
31
+ return JSON.stringify(a) === JSON.stringify(b);
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ /**
38
+ * Resolves an object's schema/definition and tracks whether that resolution
39
+ * has SETTLED for the object CURRENTLY being asked about — one piece of
40
+ * state a caller cannot observe half of.
41
+ *
42
+ * ## The shape, and why it is exactly this one
43
+ *
44
+ * This is the RESOLUTION half of the settled-schema gate four views
45
+ * (`ObjectKanban`, `ObjectView`, `ObjectCalendar`, and — pre-fix —
46
+ * `ObjectTree`) each hand-wrote (objectui#6271, #6419, #6453, #6014). Ruled
47
+ * objectui#6482 (maintainer, 2026-08-27, Option A): extract the resolution
48
+ * half as a shared, published hook; leave GATE PLACEMENT — deciding which
49
+ * effect branch actually waits on `ready` — to each component, because that
50
+ * part is genuinely component-private (`ObjectCalendar` gates only its
51
+ * `object`-provider branch and keys on the object the block RESOLVES rather
52
+ * than on `schema.objectName`, because an inline `value` data set issues no
53
+ * metadata read at all — a whole-effect gate would hold its query open on a
54
+ * resolution nothing was ever going to produce).
55
+ *
56
+ * `ready` is DERIVED at render time — `resolution !== null && resolution.key
57
+ * === key` — from a SINGLE piece of state, `{ key, def } | null`, rather than
58
+ * stored as a second, independent boolean. That is not a style choice; it is
59
+ * the fix for the defect this card exists to close. objectui#6481's
60
+ * `ObjectTree` carried the definition (`objectSchema`) and "has it settled"
61
+ * (`schemaSettled`) as two SEPARATE `useState`s. `schemaSettled` was a
62
+ * one-way latch — set `true` on first settle and never reset — so when the
63
+ * host swapped `objectName` mid-life, the fetch effect re-ran and started
64
+ * refetching the NEW object's schema, but `schemaSettled` stayed `true` from
65
+ * the OLD object's settle. The gated effect read `schemaSettled === true`
66
+ * and `objectSchema` still holding the OLD object's fields, and queried with
67
+ * the WRONG `$expand` — a stale key reading as ready.
68
+ *
69
+ * With one state value and a render-time key comparison, that failure mode
70
+ * is not merely fixed, it is UNREPRESENTABLE: the instant `key` changes,
71
+ * `resolution.key === key` is false in that very render (no effect needs to
72
+ * run first), so `ready` flips to `false` in the same commit the key
73
+ * changed — there is no window, and no second piece of state a caller could
74
+ * read out of sync with the first, because there is no second piece of
75
+ * state. A caller cannot spell "ready for the wrong object": `ready` and
76
+ * `def` are two views of one value, never independently settable.
77
+ *
78
+ * Every exit of the internal fetch effect settles the resolution — success,
79
+ * a thrown read, and "there is no source to read from" alike — because a
80
+ * caller's gated effect WAITS on `ready`. An exit that returned without
81
+ * settling would not merely skip the expansion; it would hold that gated
82
+ * query open forever.
83
+ *
84
+ * ## What this hook does NOT do
85
+ *
86
+ * It does not decide when to fetch beyond "when `key` or `dataSource`
87
+ * change", and it does not decide what a caller's OTHER effects should wait
88
+ * on. A caller that should not fetch at all for the current render (e.g.
89
+ * `ObjectCalendar`'s inline `value` provider, which issues no metadata read)
90
+ * passes `dataSource: undefined` for that render rather than a new "should
91
+ * fetch" flag — the hook already settles-with-`null` whenever `dataSource`
92
+ * is absent, which is the exact "no source to read from" outcome that case
93
+ * needs.
94
+ *
95
+ * @param key - The identity of the object THIS render is asking about — for a
96
+ * record-bound view, `resolveRecordSourceObjectName(schema, dataConfig) ?? ''`
97
+ * from `@object-ui/core`. ⛔ Do NOT re-spell that ladder inline here: six view
98
+ * plugins each carried their own copy and had drifted, which is the whole of
99
+ * what `b041b9c0c` collapsed. Choosing the key is still the caller's job — it is the
100
+ * component-private half of the original hand copies, not something this hook
101
+ * can infer — but the ladder behind it is published and shared.
102
+ * @param dataSource - The data source to read the definition from. Pass
103
+ * `undefined`/`null` for a render that should settle immediately with no
104
+ * definition (no source, or a provider that needs none) rather than adding
105
+ * a separate enable flag.
106
+ * @returns `{ ready, def }` — see {@link SettledSchema}.
107
+ *
108
+ * @example
109
+ * ```tsx
110
+ * const schemaKey = resolveRecordSourceObjectName(schema, dataConfig); // @object-ui/core
111
+ * const { ready: objectSchemaReady, def: objectSchema } =
112
+ * useSettledSchema(schemaKey ?? '', hasInlineData ? undefined : dataSource);
113
+ *
114
+ * useEffect(() => {
115
+ * // A PROVIDER test, not an object-name read — it asks "is there metadata to
116
+ * // wait for at all", which the shared reader deliberately does not answer.
117
+ * // Gate placement stays local (objectui#6482); the ladder does not.
118
+ * if (dataConfig?.provider === 'object' && !objectSchemaReady) return;
119
+ * // ...issue the record query, `buildExpandFields(objectSchema?.fields)`
120
+ * }, [objectSchemaReady, objectSchema, /* ... *\/]);
121
+ * ```
122
+ */
123
+ export function useSettledSchema(key, dataSource) {
124
+ const [resolution, setResolution] = useState(null);
125
+ useEffect(() => {
126
+ let isMounted = true;
127
+ const settleKey = key;
128
+ /**
129
+ * Settle THIS effect run's resolution — and, when the answer is one
130
+ * consumers already hold, settle it as the object they already hold.
131
+ *
132
+ * This is the PRODUCER half of `AGENTS.md` §5 commandment #10: *"a
133
+ * provider that refetches may not republish an equal payload as a new
134
+ * object, and a consumer that needs stability keys on the data, not on a
135
+ * cache"*. This hook refetches whenever the adapter identity changes, and
136
+ * the consumers its own `@example` instructs do exactly what the other
137
+ * half of that sentence asks — they key their record fetch on `def`, the
138
+ * payload, never on a memo identity. So a fresh object for an unchanged
139
+ * answer is not a wasted allocation; it is a DUPLICATE RECORD QUERY at
140
+ * every one of them, carrying the same expand set as the one before it.
141
+ * Pinned with its control in
142
+ * `useSettledSchema.equalPayload-10106.test.tsx` (objectui#10106).
143
+ *
144
+ * ⛔ The cure may NOT be moved to the consumer — the same sentence names
145
+ * keying on a cache as the wrong one, and a consumer that stopped keying
146
+ * on `def` would also stop re-querying when the definition genuinely
147
+ * changes, which the control case in that pin holds open.
148
+ *
149
+ * Bailing out is only ever safe for the SAME key: `resolution` is one
150
+ * piece of state whose `key` decides `ready`, so a settle for a different
151
+ * key must always publish (see the hook doc comment above).
152
+ */
153
+ const settle = (settledDef) => {
154
+ setResolution((prev) => prev && prev.key === settleKey && isEqualPayload(prev.def, settledDef)
155
+ ? prev
156
+ : { key: settleKey, def: settledDef });
157
+ };
158
+ const resolve = async () => {
159
+ if (!dataSource || !settleKey || typeof dataSource.getObjectSchema !== 'function') {
160
+ // No source for a definition: settle with none, so anything gated on
161
+ // `ready` still runs (unexpanded — with no schema there is no expand
162
+ // set to derive).
163
+ if (isMounted)
164
+ settle(null);
165
+ return;
166
+ }
167
+ try {
168
+ const def = await dataSource.getObjectSchema(settleKey);
169
+ if (isMounted)
170
+ settle(def);
171
+ }
172
+ catch (err) {
173
+ console.error('[useSettledSchema] getObjectSchema failed for', settleKey, err);
174
+ if (isMounted)
175
+ settle(null);
176
+ }
177
+ };
178
+ resolve();
179
+ return () => {
180
+ isMounted = false;
181
+ };
182
+ }, [key, dataSource]);
183
+ const ready = resolution !== null && resolution.key === key;
184
+ const def = ready ? resolution.def : null;
185
+ return { ready, def };
186
+ }
@@ -37,8 +37,13 @@ export interface UseViewDataResult<T = any> {
37
37
  dataSource: DataSource<T> | null;
38
38
  /** Re-fetch data with current or new params */
39
39
  refresh: (newParams?: QueryParams) => Promise<void>;
40
- /** Fetch a single record by ID */
41
- fetchOne: (id: string | number) => Promise<T | null>;
40
+ /**
41
+ * Fetch a single record by ID. The id is a `string`, as every record door on
42
+ * `DataSource` declares it — this hook hands the value straight to
43
+ * `DataSource.findOne`, so the union it used to admit was a claim the
44
+ * protocol never made (objectui#9511).
45
+ */
46
+ fetchOne: (id: string) => Promise<T | null>;
42
47
  /** Whether more records are available */
43
48
  hasMore: boolean;
44
49
  }
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { useState, useEffect, useCallback, useMemo, useContext } from 'react';
12
12
  import { resolveDataSource } from '@object-ui/core';
13
- import { SchemaRendererContext } from '../context/SchemaRendererContext';
13
+ import { SchemaRendererContext } from '../context/SchemaRendererContext.js';
14
14
  // ---------------------------------------------------------------------------
15
15
  // Hook
16
16
  // ---------------------------------------------------------------------------
@@ -6,7 +6,7 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  import { useState, useCallback, useMemo } from 'react';
9
- import { useReducedMotion } from './useReducedMotion';
9
+ import { useReducedMotion } from './useReducedMotion.js';
10
10
  // ---------------------------------------------------------------------------
11
11
  // Helpers
12
12
  // ---------------------------------------------------------------------------
package/dist/index.d.ts CHANGED
@@ -5,15 +5,22 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- export * from './SchemaRenderer';
9
- export * from './schema-input';
10
- export * from './hooks';
11
- export * from './context';
12
- export * from './LazyPluginLoader';
13
- export * from './spec-bridge';
14
- export * from './data-invalidation';
15
- export * from './element-data-source/ElementDataSourceGate';
16
- export { resolveKeyedI18nLabel } from './utils/i18n';
17
- export { extractWriteErrorMessage, isPermissionError, extractFieldErrors } from './utils/error-message';
18
- export type { WriteFieldError } from './utils/error-message';
8
+ export * from './SchemaRenderer.js';
9
+ export * from './schema-input.js';
10
+ export * from './hooks/index.js';
11
+ export * from './context/index.js';
12
+ export * from './LazyPluginLoader.js';
13
+ export * from './data-invalidation.js';
14
+ export * from './element-data-source/ElementDataSourceGate.js';
15
+ export { resolveKeyedI18nLabel } from './utils/i18n.js';
16
+ export { resolveInlineAriaProps } from './utils/inlineAria.js';
17
+ export { isConfigBag } from './utils/configBag.js';
18
+ export { reportUnresolvableVisibilityPredicate, formatUnresolvableVisibilityMessage, UNRESOLVABLE_VISIBILITY_PREFIX, UNRESOLVABLE_ENABLEMENT_PREFIX, __resetVisibilityPredicateWarnings, } from './utils/visibilityDiagnostic.js';
19
+ export type { PredicateScopeTier } from './utils/visibilityDiagnostic.js';
20
+ export type { PredicateGateKind } from './utils/visibilityDiagnostic.js';
21
+ export { extractWriteErrorMessage, isPermissionError, extractFieldErrors, classifyLoadError, declaredUserMessage } from './utils/error-message.js';
22
+ export type { WriteFieldError, LoadErrorKind } from './utils/error-message.js';
19
23
  export { I18nProvider, useObjectTranslation, useSafeTranslate, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, type I18nConfig, type I18nProviderProps, type TranslationKeys, type DateFormatOptions, type CurrencyFormatOptions, type NumberFormatOptions, } from '@object-ui/i18n';
24
+ export { NON_GRID_ROW_CEILING, applyNonGridRowCeiling } from '@object-ui/core';
25
+ export type { NonGridCeilingResult } from '@object-ui/core';
26
+ export { NonGridRowCeilingNote } from './utils/nonGridRowCeiling.js';
package/dist/index.js CHANGED
@@ -5,20 +5,64 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- export * from './SchemaRenderer';
9
- export * from './schema-input';
10
- export * from './hooks'; // will be empty for now
11
- export * from './context'; // will be empty for now
12
- export * from './LazyPluginLoader';
13
- export * from './spec-bridge';
14
- export * from './data-invalidation';
8
+ export * from './SchemaRenderer.js';
9
+ export * from './schema-input.js';
10
+ export * from './hooks/index.js'; // will be empty for now
11
+ export * from './context/index.js'; // will be empty for now
12
+ export * from './LazyPluginLoader.js';
13
+ export * from './data-invalidation.js';
15
14
  // PageComponentSchema.dataSource — mapping the spec's per-element data binding
16
15
  // onto the schema keys each object-bound block reads (objectstack#6953).
17
- export * from './element-data-source/ElementDataSourceGate';
16
+ export * from './element-data-source/ElementDataSourceGate.js';
18
17
  // i18n utilities
19
- export { resolveKeyedI18nLabel } from './utils/i18n';
18
+ export { resolveKeyedI18nLabel } from './utils/i18n.js';
19
+ // The spec's NESTED `aria` bag (`AriaPropsSchema`) → `aria-label` /
20
+ // `aria-describedby` / `role`, with `ariaLabel` resolved as an inline locale
21
+ // map (objectui#11051). Exported for the renderers in `@object-ui/components`,
22
+ // which depends on this package, so the mapping is written once. It is not the
23
+ // flat keyed reader `SchemaRenderer` applies to every node; objectui#4580 Q2-B
24
+ // keeps the two vocabularies apart.
25
+ export { resolveInlineAriaProps } from './utils/inlineAria.js';
26
+ // "Is this a real config bag?" — the ONE definition of that question
27
+ // (objectui#6761), exported at the package entry for the same reason the node-gate
28
+ // reporter above is (objectui#6038): a surface in `@object-ui/components`, which
29
+ // depends on this package, asks it too. `element:*` renderers there read their
30
+ // config out of `{ ...schema.props, ...schema.properties }`, the third channel of
31
+ // the degenerate-bag hazard objectui#6752 / objectui#6760 closed here
32
+ // (objectui#6783). A second copy one package over would be a spelling that
33
+ // `utils/configBag.pin.test.ts` — which scans `packages/react/src` — cannot see,
34
+ // which is the drift that pin exists to stop.
35
+ export { isConfigBag } from './utils/configBag.js';
36
+ // Node-gate predicate diagnostics. Exported at the package entry (objectui#6038)
37
+ // so every surface that evaluates a node `visibleWhen` reports a fault through
38
+ // ONE reporter and ONE dedupe `Set` — `page:tabs` item predicates live in
39
+ // `@object-ui/components`, which depends on this package. A second copy of the
40
+ // reporter would mean a second rate limit, and the same broken predicate would
41
+ // then be entitled to one line per package instead of one line, which is the
42
+ // property the 2026-08-25 ruling asked for.
43
+ export { reportUnresolvableVisibilityPredicate, formatUnresolvableVisibilityMessage, UNRESOLVABLE_VISIBILITY_PREFIX,
44
+ // The ENABLEMENT gate's opening line (objectui#6445). Exported beside its
45
+ // sibling for the reason that one is: an app filtering these out of its
46
+ // console transport filters by the constant, and a second line it cannot
47
+ // name is a second thing to hard-code. Not a second reporter — the emit,
48
+ // the dedupe `Set` and the reset above are still the only ones.
49
+ UNRESOLVABLE_ENABLEMENT_PREFIX, __resetVisibilityPredicateWarnings, } from './utils/visibilityDiagnostic.js';
20
50
  // Write-error surfacing utilities (shared by drag-write plugins so a failed
21
51
  // PATCH — e.g. an RLS 403 — is never silently swallowed).
22
- export { extractWriteErrorMessage, isPermissionError, extractFieldErrors } from './utils/error-message';
52
+ export { extractWriteErrorMessage, isPermissionError, extractFieldErrors, classifyLoadError, declaredUserMessage } from './utils/error-message.js';
23
53
  // Built-in i18n support
24
54
  export { I18nProvider, useObjectTranslation, useSafeTranslate, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, } from '@object-ui/i18n';
55
+ // The platform row ceiling for NON-GRID visualisations — gantt, calendar, map
56
+ // and tree (objectui#7210, maintainer ruling a′). One constant across the
57
+ // four, because a per-plugin copy would be four constants wearing one name,
58
+ // which is the thing the ruling ruled out.
59
+ //
60
+ // HOME: `@object-ui/core`, beside the `extractRecords` it wraps (objectui#7508,
61
+ // maintainer ruling A′). This entry re-exports the three names it published
62
+ // first, the same way it re-exports `@object-ui/i18n` above, and keeps only
63
+ // the React half — `NonGridRowCeilingNote`, which takes the
64
+ // `NonGridCeilingResult` and nothing else. The probe row's `+ 1` lives in
65
+ // core's `nonGridRowCeilingQuery()` and nowhere else; the constant that used
66
+ // to carry it here is deleted, not re-exported.
67
+ export { NON_GRID_ROW_CEILING, applyNonGridRowCeiling } from '@object-ui/core';
68
+ export { NonGridRowCeilingNote } from './utils/nonGridRowCeiling.js';
@@ -5,27 +5,68 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- import type { BaseSchema } from '@object-ui/types';
9
- import type { SchemaRendererProps } from './SchemaRenderer';
8
+ import type { SchemaNode } from '@object-ui/types';
9
+ import type { SchemaRendererProps } from './SchemaRenderer.js';
10
10
  /**
11
11
  * Narrow a loosely-typed metadata node onto {@link SchemaRendererProps.schema}.
12
12
  *
13
13
  * `@object-ui/types` declares `SchemaNode` as
14
- * `BaseSchema | string | number | boolean | null | undefined`, and a lot of
15
- * metadata plumbing (page regions, header-bar actions, detail tabs, view
14
+ * `DeclaredNode | string | number | boolean | null | undefined` (its object
15
+ * arm the declared-node union, objectui#11466), and a lot of metadata plumbing (page regions, header-bar actions, detail tabs, view
16
16
  * configs) is typed with it. `SchemaRenderer` deliberately does NOT declare the
17
17
  * `number` / `boolean` members — nobody should be invited to author them — so
18
18
  * forwarding such a value needs one honest step in between.
19
19
  *
20
20
  * This is that step, and it is a TOTAL function rather than a cast: it maps the
21
- * two primitive members onto their text form, which is precisely what the
22
- * renderer's own defensive branch does with them. So it changes no behaviour and
23
- * tells no lie — the value a caller forwards renders identically whether or not
24
- * it passes through here.
25
- *
26
- * It exists because the two competing repo-wide `SchemaNode` spellings
27
- * (`@object-ui/core`'s interface vs `@object-ui/types`' union) have not been
28
- * reconciled; that reconciliation is tracked separately. When it lands, the
29
- * call sites using this can go back to forwarding directly.
21
+ * two primitive members onto whatever `SchemaRenderer` would itself render for
22
+ * them. So it changes no behaviour and tells no lie — the value a caller
23
+ * forwards renders identically whether or not it passes through here.
24
+ *
25
+ * ## Why the mapping has TWO legs and not one
26
+ *
27
+ * Because the renderer's own narrowing has two, in a fixed order: the
28
+ * `!evaluatedSchema` leg returns nothing and runs FIRST, and only below it does
29
+ * the defensive branch return the value's `String` form. So the falsy
30
+ * primitives — `0`, `-0`, `NaN`, `false` — never reach that branch at all; they
31
+ * render as nothing. `!node` is exactly that set for the two members this
32
+ * function converts, so the ternary below IS the renderer's order, restated for
33
+ * a value that has to cross the type boundary as a string.
34
+ *
35
+ * ⚠️ The single-leg `String(node)` that stood here until objectui#8908 was the
36
+ * lie this paragraph used to tell. `String(0)` and `String(false)` are the
37
+ * NON-EMPTY strings `'0'` and `'false'`, which are truthy, so they sailed past
38
+ * the renderer's first leg and printed themselves — while the same two values
39
+ * handed to `SchemaRenderer` directly render nothing, pinned since
40
+ * objectui#4548 in `packages/react/src/__tests__/SchemaRenderer.primitiveSchema.test.tsx`.
41
+ * The shipped `empty` renderer, whose `action` slot gates on nullish rather than
42
+ * truthiness, printed a stray `0` for `{ type: 'empty', action: 0 }` because of
43
+ * it. ⛔ Do not "simplify" the two legs back into one.
44
+ *
45
+ * ## Why the falsy leg returns nothing rather than the value itself
46
+ *
47
+ * Passing `0` / `false` through UNCHANGED would widen this function's return
48
+ * type to admit `number` / `boolean`, and that is not a free choice: the return
49
+ * type is pinned exactly equal to `SchemaRendererProps['schema']` by
50
+ * `packages/react/src/__tests__/SchemaRenderer.propsResolution.test.ts`, which
51
+ * also pins that the prop admits NEITHER member (objectui#4548 ruling Q2). A
52
+ * widened return would therefore be a TS2322 at every call site that forwards
53
+ * it straight into `<SchemaRenderer schema={…} />`, which is all of them. The
54
+ * nullish leg is the spelling that keeps the guarantee and the type both.
55
+ *
56
+ * The bridge is PERMANENT — not scaffolding awaiting a merge. The two competing
57
+ * repo-wide `SchemaNode` spellings ARE reconciled (objectui#4580, PR #4608):
58
+ * `@object-ui/core` stopped hand-declaring its own interface and now re-exports
59
+ * `@object-ui/types`' union, so one declaration is left to disagree with. That
60
+ * reconciliation resolved in favour of the UNION, while `SchemaRenderer`'s prop
61
+ * stays deliberately narrow (objectui#4548 ruling Q2 — it declares no `number`
62
+ * or `boolean`), so a `SchemaNode` is now LESS assignable to that prop than it
63
+ * was, not more. This step therefore bridges two intentionally different types,
64
+ * and it stays. ⛔ Do not "tidy" a call site back into a direct forward: the
65
+ * five `apps/site` sites that were forwarding directly when PR #4608 landed are
66
+ * what kept `Build Docs` red on `main` for ~5 hours — each one a TS2322 naming
67
+ * `number` against this function's return type, spelled
68
+ * `string | BaseSchema | null | undefined` in objectui#4617's logs — until
69
+ * PR #4621 routed all five through here. An earlier revision of this paragraph
70
+ * said the reconciliation was still pending and invited exactly that edit.
30
71
  */
31
- export declare function toRenderableSchema(node: BaseSchema | string | number | boolean | null | undefined): SchemaRendererProps['schema'];
72
+ export declare function toRenderableSchema(node: SchemaNode): SchemaRendererProps['schema'];
@@ -9,23 +9,66 @@
9
9
  * Narrow a loosely-typed metadata node onto {@link SchemaRendererProps.schema}.
10
10
  *
11
11
  * `@object-ui/types` declares `SchemaNode` as
12
- * `BaseSchema | string | number | boolean | null | undefined`, and a lot of
13
- * metadata plumbing (page regions, header-bar actions, detail tabs, view
12
+ * `DeclaredNode | string | number | boolean | null | undefined` (its object
13
+ * arm the declared-node union, objectui#11466), and a lot of metadata plumbing (page regions, header-bar actions, detail tabs, view
14
14
  * configs) is typed with it. `SchemaRenderer` deliberately does NOT declare the
15
15
  * `number` / `boolean` members — nobody should be invited to author them — so
16
16
  * forwarding such a value needs one honest step in between.
17
17
  *
18
18
  * This is that step, and it is a TOTAL function rather than a cast: it maps the
19
- * two primitive members onto their text form, which is precisely what the
20
- * renderer's own defensive branch does with them. So it changes no behaviour and
21
- * tells no lie — the value a caller forwards renders identically whether or not
22
- * it passes through here.
23
- *
24
- * It exists because the two competing repo-wide `SchemaNode` spellings
25
- * (`@object-ui/core`'s interface vs `@object-ui/types`' union) have not been
26
- * reconciled; that reconciliation is tracked separately. When it lands, the
27
- * call sites using this can go back to forwarding directly.
19
+ * two primitive members onto whatever `SchemaRenderer` would itself render for
20
+ * them. So it changes no behaviour and tells no lie — the value a caller
21
+ * forwards renders identically whether or not it passes through here.
22
+ *
23
+ * ## Why the mapping has TWO legs and not one
24
+ *
25
+ * Because the renderer's own narrowing has two, in a fixed order: the
26
+ * `!evaluatedSchema` leg returns nothing and runs FIRST, and only below it does
27
+ * the defensive branch return the value's `String` form. So the falsy
28
+ * primitives — `0`, `-0`, `NaN`, `false` — never reach that branch at all; they
29
+ * render as nothing. `!node` is exactly that set for the two members this
30
+ * function converts, so the ternary below IS the renderer's order, restated for
31
+ * a value that has to cross the type boundary as a string.
32
+ *
33
+ * ⚠️ The single-leg `String(node)` that stood here until objectui#8908 was the
34
+ * lie this paragraph used to tell. `String(0)` and `String(false)` are the
35
+ * NON-EMPTY strings `'0'` and `'false'`, which are truthy, so they sailed past
36
+ * the renderer's first leg and printed themselves — while the same two values
37
+ * handed to `SchemaRenderer` directly render nothing, pinned since
38
+ * objectui#4548 in `packages/react/src/__tests__/SchemaRenderer.primitiveSchema.test.tsx`.
39
+ * The shipped `empty` renderer, whose `action` slot gates on nullish rather than
40
+ * truthiness, printed a stray `0` for `{ type: 'empty', action: 0 }` because of
41
+ * it. ⛔ Do not "simplify" the two legs back into one.
42
+ *
43
+ * ## Why the falsy leg returns nothing rather than the value itself
44
+ *
45
+ * Passing `0` / `false` through UNCHANGED would widen this function's return
46
+ * type to admit `number` / `boolean`, and that is not a free choice: the return
47
+ * type is pinned exactly equal to `SchemaRendererProps['schema']` by
48
+ * `packages/react/src/__tests__/SchemaRenderer.propsResolution.test.ts`, which
49
+ * also pins that the prop admits NEITHER member (objectui#4548 ruling Q2). A
50
+ * widened return would therefore be a TS2322 at every call site that forwards
51
+ * it straight into `<SchemaRenderer schema={…} />`, which is all of them. The
52
+ * nullish leg is the spelling that keeps the guarantee and the type both.
53
+ *
54
+ * The bridge is PERMANENT — not scaffolding awaiting a merge. The two competing
55
+ * repo-wide `SchemaNode` spellings ARE reconciled (objectui#4580, PR #4608):
56
+ * `@object-ui/core` stopped hand-declaring its own interface and now re-exports
57
+ * `@object-ui/types`' union, so one declaration is left to disagree with. That
58
+ * reconciliation resolved in favour of the UNION, while `SchemaRenderer`'s prop
59
+ * stays deliberately narrow (objectui#4548 ruling Q2 — it declares no `number`
60
+ * or `boolean`), so a `SchemaNode` is now LESS assignable to that prop than it
61
+ * was, not more. This step therefore bridges two intentionally different types,
62
+ * and it stays. ⛔ Do not "tidy" a call site back into a direct forward: the
63
+ * five `apps/site` sites that were forwarding directly when PR #4608 landed are
64
+ * what kept `Build Docs` red on `main` for ~5 hours — each one a TS2322 naming
65
+ * `number` against this function's return type, spelled
66
+ * `string | BaseSchema | null | undefined` in objectui#4617's logs — until
67
+ * PR #4621 routed all five through here. An earlier revision of this paragraph
68
+ * said the reconciliation was still pending and invited exactly that edit.
28
69
  */
29
70
  export function toRenderableSchema(node) {
30
- return typeof node === 'number' || typeof node === 'boolean' ? String(node) : node;
71
+ return typeof node === 'number' || typeof node === 'boolean'
72
+ ? (node ? String(node) : undefined)
73
+ : node;
31
74
  }
@@ -0,0 +1,68 @@
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 value a real config bag — an object, and not an array pretending to
10
+ * be one? (objectui#6761)
11
+ *
12
+ * This is the ONE definition of that question in `@object-ui/react`, and it
13
+ * lives here rather than in whichever module happened to need it first,
14
+ * because every module that asks it is a CONSUMER of the same authored shape:
15
+ * the `properties` / `props` bags a node may carry. `typeof null === 'object'`
16
+ * is covered by the truthiness test.
17
+ *
18
+ * ## Why one definition, when all the copies agreed
19
+ *
20
+ * They agreed; that was never the cost. The cost is that disagreement between
21
+ * them produces NO error — each copy is a boolean expression, and a copy that
22
+ * drifts answers a different question on ONE channel while every other channel
23
+ * keeps the old answer. That is the same failure mode `hasDeclaredPredicate`
24
+ * (`@object-ui/core`) was created to end for "is a gate DECLARED?", which had
25
+ * three spellings with three different SCOPES before objectui#3850 —
26
+ * `disabled: null` greying a control out on one path and not another
27
+ * (objectui#3862).
28
+ *
29
+ * ## The spellings this replaces, measured on `b98352a15`
30
+ *
31
+ * Six occurrences, four spellings, all in `packages/react/src`:
32
+ *
33
+ * 1. `SchemaRenderer`'s module-private `isConfigBag` — itself already the
34
+ * merge of the `properties` evaluation guard and `propsWithoutCanonicalKeys`
35
+ * (objectui#6752, which unified those two because it had to touch both,
36
+ * and filed the rest as objectui#6761 rather than smuggling it in);
37
+ * 2. `SchemaRenderer`'s `winningVisibilityKey`, inline, spelled with
38
+ * `!= null` where the others spell truthiness;
39
+ * 3. `utils/propsBagDiagnostic.ts`'s module-private twin (objectui#6708);
40
+ * 4. `utils/unevaluatedExpression.ts`'s `scanBag` early return, spelled
41
+ * NEGATED (objectui#4795);
42
+ * 5. `utils/unevaluatedExpression.ts`'s `hoisted`, in the same file as (4)
43
+ * and spelled positively — this one is not in objectui#6761's inventory
44
+ * of five, which counted the file once; six occurrences is the count
45
+ * measured on this branch's base, and it was six on the card's base
46
+ * (`b76ca6764`) too.
47
+ *
48
+ * `!= null` (2) and truthiness (1, 3, 5) cannot disagree HERE, and the
49
+ * difference is worth naming rather than smoothing over: they part company
50
+ * only on a falsy value, and every falsy value except `document.all` fails
51
+ * `typeof === 'object'` in the very next conjunct. The texts differ; the
52
+ * answers cannot.
53
+ *
54
+ * ## What this is NOT
55
+ *
56
+ * Not a general "is this a plain object?" utility, and deliberately not shared
57
+ * with the two sites that ask the same SHAPE question about a data ROW —
58
+ * `SchemaRenderer`'s `boundRecord` scope entry and
59
+ * `usePredicateRecordContext` (`hooks/useExpression.ts`). Those answer "did a
60
+ * row bind?", and their answer has its own pinned meaning: binding NOTHING
61
+ * rather than an empty row is what keeps a host-supplied `record` from being
62
+ * shadowed. Should "config bag" ever narrow (rejecting a class instance, say),
63
+ * the row sites must NOT follow — merging them would make that a single edit
64
+ * with two rulings behind it. The pin in `configBag.pin.test.ts` names both
65
+ * sites with this reason, so the next reader finds a decision rather than an
66
+ * oversight.
67
+ */
68
+ export declare function isConfigBag(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,70 @@
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 value a real config bag — an object, and not an array pretending to
10
+ * be one? (objectui#6761)
11
+ *
12
+ * This is the ONE definition of that question in `@object-ui/react`, and it
13
+ * lives here rather than in whichever module happened to need it first,
14
+ * because every module that asks it is a CONSUMER of the same authored shape:
15
+ * the `properties` / `props` bags a node may carry. `typeof null === 'object'`
16
+ * is covered by the truthiness test.
17
+ *
18
+ * ## Why one definition, when all the copies agreed
19
+ *
20
+ * They agreed; that was never the cost. The cost is that disagreement between
21
+ * them produces NO error — each copy is a boolean expression, and a copy that
22
+ * drifts answers a different question on ONE channel while every other channel
23
+ * keeps the old answer. That is the same failure mode `hasDeclaredPredicate`
24
+ * (`@object-ui/core`) was created to end for "is a gate DECLARED?", which had
25
+ * three spellings with three different SCOPES before objectui#3850 —
26
+ * `disabled: null` greying a control out on one path and not another
27
+ * (objectui#3862).
28
+ *
29
+ * ## The spellings this replaces, measured on `b98352a15`
30
+ *
31
+ * Six occurrences, four spellings, all in `packages/react/src`:
32
+ *
33
+ * 1. `SchemaRenderer`'s module-private `isConfigBag` — itself already the
34
+ * merge of the `properties` evaluation guard and `propsWithoutCanonicalKeys`
35
+ * (objectui#6752, which unified those two because it had to touch both,
36
+ * and filed the rest as objectui#6761 rather than smuggling it in);
37
+ * 2. `SchemaRenderer`'s `winningVisibilityKey`, inline, spelled with
38
+ * `!= null` where the others spell truthiness;
39
+ * 3. `utils/propsBagDiagnostic.ts`'s module-private twin (objectui#6708);
40
+ * 4. `utils/unevaluatedExpression.ts`'s `scanBag` early return, spelled
41
+ * NEGATED (objectui#4795);
42
+ * 5. `utils/unevaluatedExpression.ts`'s `hoisted`, in the same file as (4)
43
+ * and spelled positively — this one is not in objectui#6761's inventory
44
+ * of five, which counted the file once; six occurrences is the count
45
+ * measured on this branch's base, and it was six on the card's base
46
+ * (`b76ca6764`) too.
47
+ *
48
+ * `!= null` (2) and truthiness (1, 3, 5) cannot disagree HERE, and the
49
+ * difference is worth naming rather than smoothing over: they part company
50
+ * only on a falsy value, and every falsy value except `document.all` fails
51
+ * `typeof === 'object'` in the very next conjunct. The texts differ; the
52
+ * answers cannot.
53
+ *
54
+ * ## What this is NOT
55
+ *
56
+ * Not a general "is this a plain object?" utility, and deliberately not shared
57
+ * with the two sites that ask the same SHAPE question about a data ROW —
58
+ * `SchemaRenderer`'s `boundRecord` scope entry and
59
+ * `usePredicateRecordContext` (`hooks/useExpression.ts`). Those answer "did a
60
+ * row bind?", and their answer has its own pinned meaning: binding NOTHING
61
+ * rather than an empty row is what keeps a host-supplied `record` from being
62
+ * shadowed. Should "config bag" ever narrow (rejecting a class instance, say),
63
+ * the row sites must NOT follow — merging them would make that a single edit
64
+ * with two rulings behind it. The pin in `configBag.pin.test.ts` names both
65
+ * sites with this reason, so the next reader finds a decision rather than an
66
+ * oversight.
67
+ */
68
+ export function isConfigBag(value) {
69
+ return !!value && typeof value === 'object' && !Array.isArray(value);
70
+ }