@object-ui/react 17.6.0 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4263 -0
- package/README.md +383 -42
- package/dist/SchemaRenderer.d.ts +64 -7
- package/dist/SchemaRenderer.js +1326 -106
- package/dist/context/ActionContext.js +40 -1
- package/dist/context/AppShellContext.d.ts +27 -0
- package/dist/context/DiscussionContext.d.ts +27 -0
- package/dist/context/DiscussionContext.js +4 -0
- package/dist/context/DrillNavigationContext.d.ts +13 -0
- package/dist/context/InlineEditContext.d.ts +151 -1
- package/dist/context/InlineEditContext.js +36 -2
- package/dist/context/PrintPageContext.d.ts +60 -0
- package/dist/context/PrintPageContext.js +32 -0
- package/dist/context/RecordContext.d.ts +48 -5
- package/dist/context/RecordContext.js +10 -1
- package/dist/context/RelatedRecordActionsContext.d.ts +72 -2
- package/dist/context/RelatedRecordActionsContext.js +4 -0
- package/dist/context/SchemaRendererContext.d.ts +61 -6
- package/dist/context/SchemaRendererContext.js +39 -6
- 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 +1 -0
- package/dist/context/index.js +1 -0
- package/dist/element-data-source/ElementApiDataSourcePanel.d.ts +28 -0
- package/dist/element-data-source/ElementApiDataSourcePanel.js +10 -0
- package/dist/element-data-source/ElementDataSourceGate.d.ts +118 -3
- package/dist/element-data-source/ElementDataSourceGate.js +401 -19
- package/dist/element-data-source/useElementApiDataSource.d.ts +119 -0
- package/dist/element-data-source/useElementApiDataSource.js +198 -0
- package/dist/hooks/index.d.ts +3 -1
- package/dist/hooks/index.js +10 -1
- package/dist/hooks/useActionEngine.js +46 -3
- package/dist/hooks/useActionRunner.js +4 -0
- package/dist/hooks/useActionTextLocalizer.d.ts +35 -9
- package/dist/hooks/useActionTextLocalizer.js +70 -14
- package/dist/hooks/useConfigBagEvaluator.d.ts +31 -0
- package/dist/hooks/useConfigBagEvaluator.js +40 -0
- package/dist/hooks/useDatasetDimensionLabels.js +25 -10
- package/dist/hooks/useDensityMode.d.ts +8 -2
- package/dist/hooks/useDensityMode.js +8 -2
- package/dist/hooks/useDiscovery.d.ts +1 -10
- package/dist/hooks/useETagCache.js +25 -4
- package/dist/hooks/useElementDataSource.d.ts +15 -2
- package/dist/hooks/useElementDataSource.js +24 -7
- package/dist/hooks/useExpression.d.ts +52 -59
- package/dist/hooks/useExpression.js +58 -90
- 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 +76 -11
- package/dist/hooks/useNavigationOverlay.js +119 -9
- package/dist/hooks/useOffline.js +65 -11
- package/dist/hooks/usePageAssignment.d.ts +33 -7
- package/dist/hooks/usePageAssignment.js +85 -21
- 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 +10 -3
- package/dist/hooks/useViewData.js +5 -14
- package/dist/index.d.ts +9 -1
- package/dist/index.js +48 -1
- package/dist/schema-input.d.ts +38 -8
- package/dist/schema-input.js +39 -7
- package/dist/utils/configBag.d.ts +68 -0
- package/dist/utils/configBag.js +70 -0
- package/dist/utils/error-message.d.ts +1 -1
- package/dist/utils/error-message.js +1 -1
- package/dist/utils/inlineAria.d.ts +61 -0
- package/dist/utils/inlineAria.js +70 -0
- package/dist/utils/localeMap.d.ts +27 -0
- package/dist/utils/localeMap.js +106 -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 +422 -0
- package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
- package/dist/utils/refusedDataPropDiagnostic.js +94 -0
- package/dist/utils/unevaluatedExpression.d.ts +44 -1
- package/dist/utils/unevaluatedExpression.js +92 -11
- package/dist/utils/visibilityDiagnostic.d.ts +282 -21
- package/dist/utils/visibilityDiagnostic.js +321 -46
- 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 -158
- package/dist/spec-bridge/bridges/form-view.js +0 -170
- package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
- package/dist/spec-bridge/bridges/list-view.js +0 -268
- 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
package/dist/index.js
CHANGED
|
@@ -10,15 +10,62 @@ 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';
|
|
17
|
+
// PageComponentSchema.dataSource as the spec's API binding — the read behind
|
|
18
|
+
// the `data` root `SchemaRenderer` binds for it (objectui#12197).
|
|
19
|
+
export * from './element-data-source/useElementApiDataSource.js';
|
|
18
20
|
// i18n utilities
|
|
19
21
|
export { resolveKeyedI18nLabel } from './utils/i18n.js';
|
|
22
|
+
// The spec's NESTED `aria` bag (`AriaPropsSchema`) → `aria-label` /
|
|
23
|
+
// `aria-describedby` / `role`, with `ariaLabel` resolved as an inline locale
|
|
24
|
+
// map (objectui#11051). Exported for the renderers in `@object-ui/components`,
|
|
25
|
+
// which depends on this package, so the mapping is written once. It is not the
|
|
26
|
+
// flat keyed reader `SchemaRenderer` applies to every node; objectui#4580 Q2-B
|
|
27
|
+
// keeps the two vocabularies apart.
|
|
28
|
+
export { resolveInlineAriaProps } from './utils/inlineAria.js';
|
|
29
|
+
// "Is this a real config bag?" — the ONE definition of that question
|
|
30
|
+
// (objectui#6761), exported at the package entry for the same reason the node-gate
|
|
31
|
+
// reporter above is (objectui#6038): a surface in `@object-ui/components`, which
|
|
32
|
+
// depends on this package, asks it too. `element:*` renderers there read their
|
|
33
|
+
// config out of `{ ...schema.props, ...schema.properties }`, the third channel of
|
|
34
|
+
// the degenerate-bag hazard objectui#6752 / objectui#6760 closed here
|
|
35
|
+
// (objectui#6783). A second copy one package over would be a spelling that
|
|
36
|
+
// `utils/configBag.pin.test.ts` — which scans `packages/react/src` — cannot see,
|
|
37
|
+
// which is the drift that pin exists to stop.
|
|
38
|
+
export { isConfigBag } from './utils/configBag.js';
|
|
39
|
+
// Node-gate predicate diagnostics. Exported at the package entry (objectui#6038)
|
|
40
|
+
// so every surface that evaluates a node `visibleWhen` reports a fault through
|
|
41
|
+
// ONE reporter and ONE dedupe `Set` — `page:tabs` item predicates live in
|
|
42
|
+
// `@object-ui/components`, which depends on this package. A second copy of the
|
|
43
|
+
// reporter would mean a second rate limit, and the same broken predicate would
|
|
44
|
+
// then be entitled to one line per package instead of one line, which is the
|
|
45
|
+
// property the 2026-08-25 ruling asked for.
|
|
46
|
+
export { reportUnresolvableVisibilityPredicate, formatUnresolvableVisibilityMessage, UNRESOLVABLE_VISIBILITY_PREFIX,
|
|
47
|
+
// The ENABLEMENT gate's opening line (objectui#6445). Exported beside its
|
|
48
|
+
// sibling for the reason that one is: an app filtering these out of its
|
|
49
|
+
// console transport filters by the constant, and a second line it cannot
|
|
50
|
+
// name is a second thing to hard-code. Not a second reporter — the emit,
|
|
51
|
+
// the dedupe `Set` and the reset above are still the only ones.
|
|
52
|
+
UNRESOLVABLE_ENABLEMENT_PREFIX, __resetVisibilityPredicateWarnings, } from './utils/visibilityDiagnostic.js';
|
|
20
53
|
// Write-error surfacing utilities (shared by drag-write plugins so a failed
|
|
21
54
|
// PATCH — e.g. an RLS 403 — is never silently swallowed).
|
|
22
55
|
export { extractWriteErrorMessage, isPermissionError, extractFieldErrors, classifyLoadError, declaredUserMessage } from './utils/error-message.js';
|
|
23
56
|
// Built-in i18n support
|
|
24
57
|
export { I18nProvider, useObjectTranslation, useSafeTranslate, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, } from '@object-ui/i18n';
|
|
58
|
+
// The platform row ceiling for NON-GRID visualisations — gantt, calendar, map
|
|
59
|
+
// and tree (objectui#7210, maintainer ruling a′). One constant across the
|
|
60
|
+
// four, because a per-plugin copy would be four constants wearing one name,
|
|
61
|
+
// which is the thing the ruling ruled out.
|
|
62
|
+
//
|
|
63
|
+
// HOME: `@object-ui/core`, beside the `extractRecords` it wraps (objectui#7508,
|
|
64
|
+
// maintainer ruling A′). This entry re-exports the three names it published
|
|
65
|
+
// first, the same way it re-exports `@object-ui/i18n` above, and keeps only
|
|
66
|
+
// the React half — `NonGridRowCeilingNote`, which takes the
|
|
67
|
+
// `NonGridCeilingResult` and nothing else. The probe row's `+ 1` lives in
|
|
68
|
+
// core's `nonGridRowCeilingQuery()` and nowhere else; the constant that used
|
|
69
|
+
// to carry it here is deleted, not re-exported.
|
|
70
|
+
export { NON_GRID_ROW_CEILING, applyNonGridRowCeiling } from '@object-ui/core';
|
|
71
|
+
export { NonGridRowCeilingNote } from './utils/nonGridRowCeiling.js';
|
package/dist/schema-input.d.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
* `
|
|
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
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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:
|
|
72
|
+
export declare function toRenderableSchema(node: SchemaNode): SchemaRendererProps['schema'];
|
package/dist/schema-input.js
CHANGED
|
@@ -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
|
-
* `
|
|
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
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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'
|
|
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
|
|
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
|
|
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,27 @@
|
|
|
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 an inline locale map, in `InlineLocaleMapSchema`'s sense?
|
|
10
|
+
*
|
|
11
|
+
* The member loop first is a fast reject and not a second definition: the
|
|
12
|
+
* schema requires every value to be a string, so the loop can only turn away
|
|
13
|
+
* what the schema turns away, and it does so without the schema's per-key
|
|
14
|
+
* issue reporting. Most data objects carry a non-string member.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isInlineLocaleMap(value: unknown): value is Record<string, string>;
|
|
17
|
+
/** Visit one member. `locale` is the member's key (`en`, `zh-CN`, `default`). */
|
|
18
|
+
export type LocaleMapMemberVisitor = (member: string, locale: string) => unknown;
|
|
19
|
+
/**
|
|
20
|
+
* Map every member of an inline locale map through `visit` and return the
|
|
21
|
+
* result, spelled as a string: the map itself when nothing changed (copy on
|
|
22
|
+
* write).
|
|
23
|
+
*
|
|
24
|
+
* The evaluation memo passes the evaluator; the dev diagnostic passes a visitor
|
|
25
|
+
* that records the member and returns it unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mapLocaleMapMembers(map: Record<string, string>, visit: LocaleMapMemberVisitor): Record<string, string>;
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
* Each member of an inline locale map is evaluated like a plain string on the
|
|
10
|
+
* same key (objectui#12936).
|
|
11
|
+
*
|
|
12
|
+
* ## The rule
|
|
13
|
+
*
|
|
14
|
+
* A node's config-bag value that is an INLINE LOCALE MAP - the second
|
|
15
|
+
* authorized form of the spec's `I18nLabel`, for example
|
|
16
|
+
* `title: { en: 'Published ${data.at}', 'zh-CN': '...${data.at}' }` - has
|
|
17
|
+
* every member evaluated by the `SchemaRenderer` evaluation memo, with the same
|
|
18
|
+
* evaluator and so against the same scope as a plain string on that key:
|
|
19
|
+
* `record`, `current_user` / `user`, `page`, the host's ambient roots, and
|
|
20
|
+
* `data` on a node with an API binding. The block then picks one member for
|
|
21
|
+
* the viewer's language (`pickLocalized`) and draws an evaluated string.
|
|
22
|
+
*
|
|
23
|
+
* Before it, the map was a data object to the per-value loops, which hand a
|
|
24
|
+
* data object over as authored, so the member the block picked kept its
|
|
25
|
+
* `${...}` and drew it as text. A plain string with the same hole on the same
|
|
26
|
+
* key was evaluated, so localizing a line was what broke it.
|
|
27
|
+
*
|
|
28
|
+
* ## What a locale map is: the spec's answer, not a third one
|
|
29
|
+
*
|
|
30
|
+
* {@link isInlineLocaleMap} asks `@objectstack/spec`'s `InlineLocaleMapSchema`,
|
|
31
|
+
* the schema the contract declares the form with: every key a BCP-47 tag or
|
|
32
|
+
* `default` (never `key` / `defaultValue`), every value a string. A value with
|
|
33
|
+
* a non-string member, or with a key that is not a locale tag, is NOT a locale
|
|
34
|
+
* map: it stays a data object and is handed over as authored, with nothing
|
|
35
|
+
* inside it evaluated. No other object is walked.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ The boundary that follows from asking the contract: an object whose keys
|
|
38
|
+
* all parse as locale tags (any two or three letters do, so `{ id, url }`
|
|
39
|
+
* qualifies) and whose values are all strings IS an inline locale map to the
|
|
40
|
+
* spec, and so it is one here too, and a `${...}` in its members is evaluated.
|
|
41
|
+
*
|
|
42
|
+
* ## Why this module exists
|
|
43
|
+
*
|
|
44
|
+
* Two readers must agree on one radius, as they do for `params`
|
|
45
|
+
* (`paramsBag.ts`): the memo that EVALUATES the members, and the dev
|
|
46
|
+
* diagnostic (`unevaluatedExpression.ts`) that REPORTS a member still carrying
|
|
47
|
+
* `${...}` afterwards because its expression threw. Both import this module, so
|
|
48
|
+
* a member that fails to evaluate is reported exactly as a plain string that
|
|
49
|
+
* fails is.
|
|
50
|
+
*
|
|
51
|
+
* ## The result is still a locale map
|
|
52
|
+
*
|
|
53
|
+
* A member's result is spelled as a string. A member that is one whole hole,
|
|
54
|
+
* `'${data.count}'`, evaluates to the number itself, as a plain string does;
|
|
55
|
+
* left a number, the member would be skipped by every `pickLocalized` limb
|
|
56
|
+
* (each reads string entries only) and the viewer would get another locale's
|
|
57
|
+
* line, or nothing. `String(result ?? '')` is the spelling the evaluator
|
|
58
|
+
* already gives an interpolated part of a plain string.
|
|
59
|
+
*
|
|
60
|
+
* ## Copy on write
|
|
61
|
+
*
|
|
62
|
+
* The map is copied only when a member changed, so a map without a hole comes
|
|
63
|
+
* back as the very object that went in, and the authored map is never mutated.
|
|
64
|
+
* Nothing may depend on that identity (AGENTS.md #10).
|
|
65
|
+
*/
|
|
66
|
+
import { InlineLocaleMapSchema } from '@objectstack/spec/ui';
|
|
67
|
+
import { isConfigBag } from './configBag.js';
|
|
68
|
+
/**
|
|
69
|
+
* Is this value an inline locale map, in `InlineLocaleMapSchema`'s sense?
|
|
70
|
+
*
|
|
71
|
+
* The member loop first is a fast reject and not a second definition: the
|
|
72
|
+
* schema requires every value to be a string, so the loop can only turn away
|
|
73
|
+
* what the schema turns away, and it does so without the schema's per-key
|
|
74
|
+
* issue reporting. Most data objects carry a non-string member.
|
|
75
|
+
*/
|
|
76
|
+
export function isInlineLocaleMap(value) {
|
|
77
|
+
if (!isConfigBag(value))
|
|
78
|
+
return false;
|
|
79
|
+
for (const member of Object.values(value)) {
|
|
80
|
+
if (typeof member !== 'string')
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return InlineLocaleMapSchema.safeParse(value).success;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Map every member of an inline locale map through `visit` and return the
|
|
87
|
+
* result, spelled as a string: the map itself when nothing changed (copy on
|
|
88
|
+
* write).
|
|
89
|
+
*
|
|
90
|
+
* The evaluation memo passes the evaluator; the dev diagnostic passes a visitor
|
|
91
|
+
* that records the member and returns it unchanged.
|
|
92
|
+
*/
|
|
93
|
+
export function mapLocaleMapMembers(map, visit) {
|
|
94
|
+
let copy;
|
|
95
|
+
for (const locale of Object.keys(map)) {
|
|
96
|
+
const member = map[locale];
|
|
97
|
+
const result = visit(member, locale);
|
|
98
|
+
const next = typeof result === 'string' ? result : String(result ?? '');
|
|
99
|
+
if (next !== member) {
|
|
100
|
+
if (!copy)
|
|
101
|
+
copy = { ...map };
|
|
102
|
+
copy[locale] = next;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return copy ?? map;
|
|
106
|
+
}
|