@object-ui/react 17.6.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 (89) hide show
  1. package/CHANGELOG.md +3766 -0
  2. package/README.md +294 -38
  3. package/dist/SchemaRenderer.d.ts +38 -7
  4. package/dist/SchemaRenderer.js +1218 -87
  5. package/dist/context/ActionContext.js +40 -1
  6. package/dist/context/DiscussionContext.d.ts +12 -0
  7. package/dist/context/DiscussionContext.js +2 -0
  8. package/dist/context/DrillNavigationContext.d.ts +13 -0
  9. package/dist/context/InlineEditContext.d.ts +81 -1
  10. package/dist/context/InlineEditContext.js +8 -2
  11. package/dist/context/RecordContext.d.ts +48 -5
  12. package/dist/context/RecordContext.js +10 -1
  13. package/dist/context/RelatedRecordActionsContext.d.ts +5 -1
  14. package/dist/context/SchemaRendererContext.d.ts +54 -2
  15. package/dist/context/SchemaRendererContext.js +38 -5
  16. package/dist/context/ThemeContext.js +1 -1
  17. package/dist/context/actionRunnerTranslator.d.ts +27 -0
  18. package/dist/context/actionRunnerTranslator.js +37 -0
  19. package/dist/element-data-source/ElementDataSourceGate.d.ts +88 -1
  20. package/dist/element-data-source/ElementDataSourceGate.js +326 -13
  21. package/dist/hooks/index.d.ts +3 -1
  22. package/dist/hooks/index.js +10 -1
  23. package/dist/hooks/useActionEngine.js +46 -3
  24. package/dist/hooks/useActionRunner.js +4 -0
  25. package/dist/hooks/useActionTextLocalizer.d.ts +21 -8
  26. package/dist/hooks/useActionTextLocalizer.js +33 -8
  27. package/dist/hooks/useConfigBagEvaluator.d.ts +30 -0
  28. package/dist/hooks/useConfigBagEvaluator.js +39 -0
  29. package/dist/hooks/useDensityMode.d.ts +6 -2
  30. package/dist/hooks/useDensityMode.js +6 -2
  31. package/dist/hooks/useDiscovery.d.ts +1 -10
  32. package/dist/hooks/useETagCache.js +25 -4
  33. package/dist/hooks/useElementDataSource.d.ts +15 -2
  34. package/dist/hooks/useElementDataSource.js +24 -7
  35. package/dist/hooks/useExpression.d.ts +52 -59
  36. package/dist/hooks/useExpression.js +58 -90
  37. package/dist/hooks/useFilterScope.d.ts +22 -2
  38. package/dist/hooks/useFilterScope.js +29 -4
  39. package/dist/hooks/useGlobalUndo.d.ts +10 -0
  40. package/dist/hooks/useGlobalUndo.js +112 -46
  41. package/dist/hooks/useNavigationOverlay.d.ts +76 -11
  42. package/dist/hooks/useNavigationOverlay.js +119 -9
  43. package/dist/hooks/useOffline.js +65 -11
  44. package/dist/hooks/usePageAssignment.d.ts +8 -5
  45. package/dist/hooks/usePageAssignment.js +37 -16
  46. package/dist/hooks/useRecordSearch.d.ts +34 -1
  47. package/dist/hooks/useRecordSearch.js +42 -7
  48. package/dist/hooks/useResolvedFilter.d.ts +46 -0
  49. package/dist/hooks/useResolvedFilter.js +130 -0
  50. package/dist/hooks/useSchemaPersistence.d.ts +7 -0
  51. package/dist/hooks/useSchemaPersistence.js +144 -5
  52. package/dist/hooks/useSettledSchema.d.ts +123 -0
  53. package/dist/hooks/useSettledSchema.js +186 -0
  54. package/dist/hooks/useViewData.d.ts +7 -2
  55. package/dist/index.d.ts +8 -1
  56. package/dist/index.js +45 -1
  57. package/dist/schema-input.d.ts +38 -8
  58. package/dist/schema-input.js +39 -7
  59. package/dist/utils/configBag.d.ts +68 -0
  60. package/dist/utils/configBag.js +70 -0
  61. package/dist/utils/error-message.d.ts +1 -1
  62. package/dist/utils/error-message.js +1 -1
  63. package/dist/utils/inlineAria.d.ts +61 -0
  64. package/dist/utils/inlineAria.js +70 -0
  65. package/dist/utils/nonGridRowCeiling.d.ts +44 -0
  66. package/dist/utils/nonGridRowCeiling.js +78 -0
  67. package/dist/utils/paramsBag.d.ts +28 -0
  68. package/dist/utils/paramsBag.js +146 -0
  69. package/dist/utils/propsBagDiagnostic.d.ts +196 -0
  70. package/dist/utils/propsBagDiagnostic.js +421 -0
  71. package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
  72. package/dist/utils/refusedDataPropDiagnostic.js +94 -0
  73. package/dist/utils/unevaluatedExpression.d.ts +42 -1
  74. package/dist/utils/unevaluatedExpression.js +74 -11
  75. package/dist/utils/visibilityDiagnostic.d.ts +271 -19
  76. package/dist/utils/visibilityDiagnostic.js +311 -44
  77. package/package.json +10 -10
  78. package/dist/hooks/useClientNotifications.d.ts +0 -47
  79. package/dist/hooks/useClientNotifications.js +0 -162
  80. package/dist/spec-bridge/SpecBridge.d.ts +0 -28
  81. package/dist/spec-bridge/SpecBridge.js +0 -56
  82. package/dist/spec-bridge/bridges/form-view.d.ts +0 -158
  83. package/dist/spec-bridge/bridges/form-view.js +0 -170
  84. package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
  85. package/dist/spec-bridge/bridges/list-view.js +0 -268
  86. package/dist/spec-bridge/index.d.ts +0 -11
  87. package/dist/spec-bridge/index.js +0 -10
  88. package/dist/spec-bridge/types.d.ts +0 -55
  89. package/dist/spec-bridge/types.js +0 -8
package/dist/index.d.ts CHANGED
@@ -10,10 +10,17 @@ export * from './schema-input.js';
10
10
  export * from './hooks/index.js';
11
11
  export * from './context/index.js';
12
12
  export * from './LazyPluginLoader.js';
13
- export * from './spec-bridge/index.js';
14
13
  export * from './data-invalidation.js';
15
14
  export * from './element-data-source/ElementDataSourceGate.js';
16
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';
17
21
  export { extractWriteErrorMessage, isPermissionError, extractFieldErrors, classifyLoadError, declaredUserMessage } from './utils/error-message.js';
18
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
@@ -10,15 +10,59 @@ export * from './schema-input.js';
10
10
  export * from './hooks/index.js'; // will be empty for now
11
11
  export * from './context/index.js'; // will be empty for now
12
12
  export * from './LazyPluginLoader.js';
13
- export * from './spec-bridge/index.js';
14
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
16
  export * from './element-data-source/ElementDataSourceGate.js';
18
17
  // i18n utilities
19
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
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,23 +5,53 @@
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';
8
+ import type { SchemaNode } from '@object-ui/types';
9
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.
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.
25
55
  *
26
56
  * The bridge is PERMANENT — not scaffolding awaiting a merge. The two competing
27
57
  * repo-wide `SchemaNode` spellings ARE reconciled (objectui#4580, PR #4608):
@@ -39,4 +69,4 @@ import type { SchemaRendererProps } from './SchemaRenderer.js';
39
69
  * PR #4621 routed all five through here. An earlier revision of this paragraph
40
70
  * said the reconciliation was still pending and invited exactly that edit.
41
71
  */
42
- export declare function toRenderableSchema(node: BaseSchema | string | number | boolean | null | undefined): SchemaRendererProps['schema'];
72
+ export declare function toRenderableSchema(node: SchemaNode): SchemaRendererProps['schema'];
@@ -9,17 +9,47 @@
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.
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.
23
53
  *
24
54
  * The bridge is PERMANENT — not scaffolding awaiting a merge. The two competing
25
55
  * repo-wide `SchemaNode` spellings ARE reconciled (objectui#4580, PR #4608):
@@ -38,5 +68,7 @@
38
68
  * said the reconciliation was still pending and invited exactly that edit.
39
69
  */
40
70
  export function toRenderableSchema(node) {
41
- 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;
42
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
+ }
@@ -16,7 +16,7 @@ export declare function extractWriteErrorMessage(err: unknown): string | null;
16
16
  * The refusal text the PRODUCER explicitly marked as addressed to the end user,
17
17
  * or `null` when the refusal carries no marking.
18
18
  *
19
- * `userMessage` is the opt-in channel added by objectstack#9934 (maintainer
19
+ * `userMessage` is the opt-in channel added by objectstack `79c46da90` (maintainer
20
20
  * ruling 2026-08-19 on objectui#5210), declared on `ApiErrorSchema` /
21
21
  * `EnhancedApiErrorSchema` in the pinned `@objectstack/spec`.
22
22
  * Three properties of the contract decide this reader's whole shape:
@@ -74,7 +74,7 @@ export function extractWriteErrorMessage(err) {
74
74
  * The refusal text the PRODUCER explicitly marked as addressed to the end user,
75
75
  * or `null` when the refusal carries no marking.
76
76
  *
77
- * `userMessage` is the opt-in channel added by objectstack#9934 (maintainer
77
+ * `userMessage` is the opt-in channel added by objectstack `79c46da90` (maintainer
78
78
  * ruling 2026-08-19 on objectui#5210), declared on `ApiErrorSchema` /
79
79
  * `EnhancedApiErrorSchema` in the pinned `@objectstack/spec`.
80
80
  * Three properties of the contract decide this reader's whole shape:
@@ -0,0 +1,61 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ import type { AriaProps } from '@objectstack/spec/ui';
9
+ /**
10
+ * Resolves the spec's NESTED `aria` bag to the DOM attributes it declares
11
+ * (objectui#11051).
12
+ *
13
+ * The bag is `@objectstack/spec`'s `AriaPropsSchema`, the `aria` member a
14
+ * block's own props schema carries (`element:text`, `element:button`,
15
+ * `element:image` and `element:number` among them). It declares three keys,
16
+ * and each maps to one attribute:
17
+ *
18
+ * `ariaLabel` → `aria-label`, resolved with the spec's own
19
+ * `resolveI18nLabel` against `locale`: a plain string is
20
+ * used as written, and an inline locale map
21
+ * (`{ en: 'Order total', 'zh-CN': '订单合计' }`) gives the
22
+ * entry for the viewer's language;
23
+ * `ariaDescribedBy` → `aria-describedby`;
24
+ * `role` → `role`.
25
+ *
26
+ * A key that resolves to nothing, or to an empty string, is left out of the
27
+ * result instead of being written as an empty or `undefined` attribute. A key
28
+ * the bag does not declare is not read: the spec refuses such keys when the
29
+ * document is parsed, so this function adds no alias for them.
30
+ *
31
+ * ## Two vocabularies, two readers (objectui#4580 Q2-B)
32
+ *
33
+ * This is NOT `SchemaRenderer`'s `resolveAriaProps`. That one reads the FLAT
34
+ * node keys (`BaseSchema.ariaLabel` / `ariaDescribedBy` / `role`) and resolves
35
+ * `ariaLabel` in objectui's KEYED vocabulary (`{ key, defaultValue?, params? }`,
36
+ * via `resolveKeyedI18nLabel`). The nested bag uses the INLINE locale map.
37
+ * Neither resolver accepts the other's shape: the keyed one returns `undefined`
38
+ * for a locale map, and the spec's one treats `key` / `defaultValue` as locale
39
+ * tags. So the two readers stay separate, as objectui#4580 Q2-B ruled.
40
+ *
41
+ * ## Why a pure function and not a hook
42
+ *
43
+ * The caller passes the locale; this function reads no React context. That
44
+ * lets a renderer call it anywhere in its body, including after an early
45
+ * return, and lets code outside React call it too. Renderers pass
46
+ * `useDisplayLocale()` from `@object-ui/i18n`, the locale every
47
+ * `resolveI18nLabel` read site in this repo resolves against (objectui#4580
48
+ * revised Q1-A). Like the spec's resolver, `locale` is a required parameter
49
+ * that may be `undefined` (which resolves as `en`), so a caller cannot forget
50
+ * it by accident.
51
+ *
52
+ * @param aria - The authored bag, taken directly from the node's config. A
53
+ * missing bag, or a value that is not an object, gives `{}`.
54
+ * @param locale - BCP-47 tag of the viewer's language, e.g. `zh-CN`.
55
+ * @returns The attributes to spread onto the element that carries the name.
56
+ */
57
+ export declare function resolveInlineAriaProps(aria: AriaProps | null | undefined, locale: string | undefined): {
58
+ 'aria-label'?: string;
59
+ 'aria-describedby'?: string;
60
+ role?: string;
61
+ };
@@ -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
+ import { resolveI18nLabel as resolveInlineI18nLabel } from '@objectstack/spec/ui';
9
+ /**
10
+ * Resolves the spec's NESTED `aria` bag to the DOM attributes it declares
11
+ * (objectui#11051).
12
+ *
13
+ * The bag is `@objectstack/spec`'s `AriaPropsSchema`, the `aria` member a
14
+ * block's own props schema carries (`element:text`, `element:button`,
15
+ * `element:image` and `element:number` among them). It declares three keys,
16
+ * and each maps to one attribute:
17
+ *
18
+ * `ariaLabel` → `aria-label`, resolved with the spec's own
19
+ * `resolveI18nLabel` against `locale`: a plain string is
20
+ * used as written, and an inline locale map
21
+ * (`{ en: 'Order total', 'zh-CN': '订单合计' }`) gives the
22
+ * entry for the viewer's language;
23
+ * `ariaDescribedBy` → `aria-describedby`;
24
+ * `role` → `role`.
25
+ *
26
+ * A key that resolves to nothing, or to an empty string, is left out of the
27
+ * result instead of being written as an empty or `undefined` attribute. A key
28
+ * the bag does not declare is not read: the spec refuses such keys when the
29
+ * document is parsed, so this function adds no alias for them.
30
+ *
31
+ * ## Two vocabularies, two readers (objectui#4580 Q2-B)
32
+ *
33
+ * This is NOT `SchemaRenderer`'s `resolveAriaProps`. That one reads the FLAT
34
+ * node keys (`BaseSchema.ariaLabel` / `ariaDescribedBy` / `role`) and resolves
35
+ * `ariaLabel` in objectui's KEYED vocabulary (`{ key, defaultValue?, params? }`,
36
+ * via `resolveKeyedI18nLabel`). The nested bag uses the INLINE locale map.
37
+ * Neither resolver accepts the other's shape: the keyed one returns `undefined`
38
+ * for a locale map, and the spec's one treats `key` / `defaultValue` as locale
39
+ * tags. So the two readers stay separate, as objectui#4580 Q2-B ruled.
40
+ *
41
+ * ## Why a pure function and not a hook
42
+ *
43
+ * The caller passes the locale; this function reads no React context. That
44
+ * lets a renderer call it anywhere in its body, including after an early
45
+ * return, and lets code outside React call it too. Renderers pass
46
+ * `useDisplayLocale()` from `@object-ui/i18n`, the locale every
47
+ * `resolveI18nLabel` read site in this repo resolves against (objectui#4580
48
+ * revised Q1-A). Like the spec's resolver, `locale` is a required parameter
49
+ * that may be `undefined` (which resolves as `en`), so a caller cannot forget
50
+ * it by accident.
51
+ *
52
+ * @param aria - The authored bag, taken directly from the node's config. A
53
+ * missing bag, or a value that is not an object, gives `{}`.
54
+ * @param locale - BCP-47 tag of the viewer's language, e.g. `zh-CN`.
55
+ * @returns The attributes to spread onto the element that carries the name.
56
+ */
57
+ export function resolveInlineAriaProps(aria, locale) {
58
+ if (!aria || typeof aria !== 'object')
59
+ return {};
60
+ const out = {};
61
+ const label = resolveInlineI18nLabel(aria.ariaLabel, locale);
62
+ if (label)
63
+ out['aria-label'] = label;
64
+ if (typeof aria.ariaDescribedBy === 'string' && aria.ariaDescribedBy) {
65
+ out['aria-describedby'] = aria.ariaDescribedBy;
66
+ }
67
+ if (typeof aria.role === 'string' && aria.role)
68
+ out.role = aria.role;
69
+ return out;
70
+ }
@@ -0,0 +1,44 @@
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 React from 'react';
9
+ import type { NonGridCeilingResult } from '@object-ui/core';
10
+ /**
11
+ * The loud footnote a non-grid view shows when it drew only the first
12
+ * `NON_GRID_ROW_CEILING` rows of a larger result set (objectui#7210).
13
+ *
14
+ * Placement and tone follow objectui#7148's chart footnote — a `role="note"`
15
+ * line in muted small type directly under the visualisation, naming BOTH
16
+ * numbers, because the count is the half a reader cannot recover from the
17
+ * picture. A truncated schedule renders as a healthy, confident schedule of a
18
+ * fraction of itself; "some rows are missing" leaves it indistinguishable from
19
+ * a complete one, and `2,000 of 40,000` is the bit that was missing.
20
+ *
21
+ * Two sentences because there are two conditions: a known total states the
22
+ * fact with both numbers; an adapter that reported no `total` still gets a
23
+ * DEFINITE sentence (the probe row proves more rows exist), it simply cannot
24
+ * name how many.
25
+ *
26
+ * ## It takes the RESULT, and nothing else (objectui#7508, ruling A′)
27
+ *
28
+ * The only prop is the `NonGridCeilingResult` that `applyNonGridRowCeiling`
29
+ * returned. Every number the note prints is read off that one object — the
30
+ * drawn count is `result.rows.length`, the total is `result.total`, the
31
+ * verdict is `result.truncated` — so a footnote whose numbers disagree with
32
+ * the rows actually drawn is not something a caller can write. The earlier
33
+ * shape took `drawn` / `total` / `truncated` as three loose props, and every
34
+ * call site hand-wrote `drawn={NON_GRID_ROW_CEILING}` next to a `total` from
35
+ * somewhere else: an incoherent pair was expressible and nothing refused it.
36
+ * ⛔ No deprecated path keeps those props; there were no released consumers.
37
+ *
38
+ * Renders `null` when nothing was truncated, so a caller holding a result can
39
+ * mount it unconditionally and gains no wrapper element on the healthy path.
40
+ */
41
+ export declare function NonGridRowCeilingNote({ result, }: {
42
+ /** What `applyNonGridRowCeiling` returned for the rows being drawn. */
43
+ result: NonGridCeilingResult;
44
+ }): React.JSX.Element | null;
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useObjectTranslation, en } from '@object-ui/i18n';
3
+ // The non-React half of the non-grid row ceiling — `NON_GRID_ROW_CEILING`,
4
+ // `nonGridRowCeilingQuery`, `applyNonGridRowCeiling` and `NonGridCeilingResult`
5
+ // — lives in `@object-ui/core` beside the `extractRecords` it wraps
6
+ // (objectui#7508, ruling A′). Only the footnote is React, so only the footnote
7
+ // is here.
8
+ /**
9
+ * The loud footnote a non-grid view shows when it drew only the first
10
+ * `NON_GRID_ROW_CEILING` rows of a larger result set (objectui#7210).
11
+ *
12
+ * Placement and tone follow objectui#7148's chart footnote — a `role="note"`
13
+ * line in muted small type directly under the visualisation, naming BOTH
14
+ * numbers, because the count is the half a reader cannot recover from the
15
+ * picture. A truncated schedule renders as a healthy, confident schedule of a
16
+ * fraction of itself; "some rows are missing" leaves it indistinguishable from
17
+ * a complete one, and `2,000 of 40,000` is the bit that was missing.
18
+ *
19
+ * Two sentences because there are two conditions: a known total states the
20
+ * fact with both numbers; an adapter that reported no `total` still gets a
21
+ * DEFINITE sentence (the probe row proves more rows exist), it simply cannot
22
+ * name how many.
23
+ *
24
+ * ## It takes the RESULT, and nothing else (objectui#7508, ruling A′)
25
+ *
26
+ * The only prop is the `NonGridCeilingResult` that `applyNonGridRowCeiling`
27
+ * returned. Every number the note prints is read off that one object — the
28
+ * drawn count is `result.rows.length`, the total is `result.total`, the
29
+ * verdict is `result.truncated` — so a footnote whose numbers disagree with
30
+ * the rows actually drawn is not something a caller can write. The earlier
31
+ * shape took `drawn` / `total` / `truncated` as three loose props, and every
32
+ * call site hand-wrote `drawn={NON_GRID_ROW_CEILING}` next to a `total` from
33
+ * somewhere else: an incoherent pair was expressible and nothing refused it.
34
+ * ⛔ No deprecated path keeps those props; there were no released consumers.
35
+ *
36
+ * Renders `null` when nothing was truncated, so a caller holding a result can
37
+ * mount it unconditionally and gains no wrapper element on the healthy path.
38
+ */
39
+ export function NonGridRowCeilingNote({ result, }) {
40
+ // ⚠️ A HOOK AT RENDER, never a module-scope `createSafeTranslation(...)`
41
+ // factory, and that is a correctness constraint rather than taste. This
42
+ // module is re-exported from `@object-ui/react`'s entry, so anything at its
43
+ // module scope runs on IMPORT for every consumer that touches the barrel —
44
+ // and a factory call there throws inside any test that partially mocks
45
+ // `@object-ui/i18n` with an object literal instead of `importOriginal`,
46
+ // before a single assertion runs. Two such files went red in CI on exactly
47
+ // that, and the population is repo-wide rather than knowable from here, so
48
+ // the call moved to where it cannot fire at import time. `en` is likewise
49
+ // dereferenced HERE, at render, not in a module-scope constant.
50
+ //
51
+ // `useObjectTranslation` is the one that interpolates on the provider-less
52
+ // path too (objectui#6219), so `{{shown}}` / `{{total}}` are filled whether
53
+ // or not the host mounted an `I18nProvider`. The `defaultValue` is read from
54
+ // the `en` pack rather than retyped: both sit in the same eagerly-loaded
55
+ // chunk, so a hand copy would ship identical bytes twice, and reading the
56
+ // pack makes an inline default that disagrees with `en` unrepresentable
57
+ // instead of merely policed.
58
+ const { t } = useObjectTranslation();
59
+ if (!result.truncated)
60
+ return null;
61
+ const shown = result.rows.length;
62
+ const text = typeof result.total === 'number'
63
+ ? t('common.rowCeilingNote', {
64
+ shown,
65
+ total: result.total,
66
+ defaultValue: en.common.rowCeilingNote,
67
+ })
68
+ : t('common.rowCeilingNoteUnknownTotal', {
69
+ shown,
70
+ defaultValue: en.common.rowCeilingNoteUnknownTotal,
71
+ });
72
+ // `shrink-0` is the note's own, not a caller's: under a `flex-1` view pane in
73
+ // a fixed-height host a plain sibling can be clipped out of view — the
74
+ // construction objectui#7148's `ChartFootnote` measured, and the reason the
75
+ // gantt used to pass this exact class in. Outside a flex container it does
76
+ // nothing, so all four views take it and no caller needs a styling prop.
77
+ return (_jsx("p", { role: "note", "data-row-ceiling-note": "non-grid", className: "shrink-0 px-1 py-1 text-xs text-muted-foreground", children: text }));
78
+ }