@object-ui/core 17.1.0 → 17.2.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 +526 -0
- package/dist/actions/ActionEngine.d.ts +11 -1
- package/dist/actions/ActionEngine.js +14 -3
- package/dist/actions/ActionRunner.d.ts +117 -10
- package/dist/actions/ActionRunner.js +25 -3
- package/dist/actions/actionKeys.d.ts +28 -11
- package/dist/actions/actionKeys.js +50 -10
- package/dist/query/query-ast.d.ts +6 -6
- package/dist/query/query-ast.js +2 -2
- package/dist/registry/PluginSystem.d.ts +15 -5
- package/dist/registry/PluginSystem.js +1 -1
- package/dist/registry/WidgetRegistry.d.ts +7 -7
- package/dist/registry/WidgetRegistry.js +1 -1
- package/dist/styling/scoped-styles.d.ts +14 -8
- package/dist/utils/chart-series.d.ts +14 -2
- package/dist/utils/dashboard-filters.d.ts +9 -0
- package/dist/utils/dashboard-filters.js +99 -4
- package/dist/utils/dataset-format.d.ts +13 -1
- package/dist/utils/dataset-format.js +10 -2
- package/dist/utils/filter-tokens.d.ts +13 -6
- package/dist/utils/filter-tokens.js +13 -6
- package/dist/utils/freeze-schema.d.ts +14 -6
- package/dist/utils/freeze-schema.js +14 -6
- package/dist/utils/managedBy.d.ts +78 -50
- package/dist/utils/managedBy.js +75 -52
- package/dist/utils/normalize-list-view.d.ts +12 -2
- package/dist/validation/index.d.ts +1 -0
- package/dist/validation/index.js +1 -0
- package/dist/validation/required-presence.d.ts +31 -0
- package/dist/validation/required-presence.js +36 -0
- package/dist/validation/schema-validator.d.ts +24 -8
- package/package.json +4 -4
package/dist/utils/managedBy.js
CHANGED
|
@@ -1,38 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* managedBy — the
|
|
3
|
-
*
|
|
2
|
+
* managedBy — the UI end of the platform's `managedBy` → CRUD affordance
|
|
3
|
+
* contract (ADR-0103).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* (`@objectstack/spec/data/object.zod.ts`, ADR-0103). Lives in `@object-ui/core`
|
|
7
|
-
* — React-free and reachable by every UI package (app-shell, plugin-detail,
|
|
8
|
-
* plugin-form, plugin-grid) — so the bucket logic is defined ONCE instead of
|
|
9
|
-
* hand-mirrored in each. Components ask "should I show New / Import / Edit /
|
|
10
|
-
* Delete / Export?" and get an answer that tracks the object's lifecycle without
|
|
11
|
-
* special-casing `sys_*` names.
|
|
5
|
+
* ## What is spec-owned here, and what is not (objectstack#4115)
|
|
12
6
|
*
|
|
13
|
-
* The bucket
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
7
|
+
* The bucket logic is **the spec's**, and this module now says so in code
|
|
8
|
+
* rather than in a comment. `CrudAffordances` / `RowCrudPredicates` are
|
|
9
|
+
* re-exported from `@objectstack/spec/data`, and the bucket resolution itself
|
|
10
|
+
* delegates to the spec's `resolveCrudAffordances()`.
|
|
11
|
+
*
|
|
12
|
+
* This module used to describe itself as a "UI-side mirror" of that function
|
|
13
|
+
* and carry its own `DEFAULTS` table — a line-for-line copy of the spec's
|
|
14
|
+
* `CRUD_AFFORDANCE_DEFAULTS`, plus a copy of its override parser. A faithful
|
|
15
|
+
* copy passes every value comparison and every behavioural test, so nothing
|
|
16
|
+
* would have caught the release in which the spec re-buckets an object class;
|
|
17
|
+
* reference identity is the only check that distinguishes a re-export from a
|
|
18
|
+
* fork (objectui#3003).
|
|
19
|
+
*
|
|
20
|
+
* What is genuinely objectui's and keeps a local name:
|
|
21
|
+
* {@link resolveEffectiveCrudAffordances} — the spec's object-level matrix
|
|
22
|
+
* INTERSECTED with the server-resolved effective API operation set (#3391), so
|
|
23
|
+
* the UI never offers a button the server would 405. The spec's function has
|
|
24
|
+
* no such notion; naming ours after it was the misdescription this burn-down
|
|
25
|
+
* removes.
|
|
26
|
+
*
|
|
27
|
+
* Lives in `@object-ui/core` — React-free and reachable by every UI package
|
|
28
|
+
* (app-shell, plugin-detail, plugin-form, plugin-grid) — so the intersection is
|
|
29
|
+
* defined ONCE instead of hand-mirrored in each. The bucket UNION type lives in
|
|
30
|
+
* `@object-ui/types` (`ManagedByBucket`) and is re-exported here for
|
|
31
|
+
* convenience. All translated copy (badge variants, empty-state messages) stays
|
|
32
|
+
* in `@object-ui/app-shell`.
|
|
17
33
|
*/
|
|
18
|
-
|
|
19
|
-
platform: { create: true, import: true, edit: true, delete: true, exportCsv: true },
|
|
20
|
-
config: { create: true, import: false, edit: true, delete: true, exportCsv: true },
|
|
21
|
-
system: { create: false, import: false, edit: false, delete: false, exportCsv: true },
|
|
22
|
-
'engine-owned': { create: false, import: false, edit: false, delete: false, exportCsv: true },
|
|
23
|
-
'append-only': { create: false, import: false, edit: false, delete: false, exportCsv: true },
|
|
24
|
-
'better-auth': { create: false, import: false, edit: false, delete: false, exportCsv: true },
|
|
25
|
-
};
|
|
34
|
+
import { resolveCrudAffordances as resolveSpecCrudAffordances, } from '@objectstack/spec/data';
|
|
26
35
|
/**
|
|
27
36
|
* Collapse an `edit`/`delete` override (boolean or #2614 object form) onto
|
|
28
37
|
* the given bucket default, surfacing any per-record predicates alongside.
|
|
29
38
|
*
|
|
30
39
|
* THE single parser for the `userActions.{edit,delete}` override shape — every
|
|
31
|
-
* UI package (grid row affordances, related-list row predicates
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
40
|
+
* UI package (grid row affordances, related-list row predicates) routes through
|
|
41
|
+
* here instead of re-implementing the boolean/object-form parse locally. The
|
|
42
|
+
* spec keeps its equivalent (`normalizeRowCrudOverride`) module-private, so
|
|
43
|
+
* this stays a local declaration — under a name the spec does not own.
|
|
44
|
+
*
|
|
45
|
+
* `base` is the bucket default an omitted `enabled` falls back to (callers that
|
|
46
|
+
* only gate on an explicit opt-out pass `true`; predicate extraction is
|
|
47
|
+
* independent of `base`).
|
|
36
48
|
*/
|
|
37
49
|
export function normalizeUserAction(v, base) {
|
|
38
50
|
if (v == null)
|
|
@@ -73,7 +85,15 @@ const AFFORDANCE_TO_API_OPERATION = {
|
|
|
73
85
|
exportCsv: 'export',
|
|
74
86
|
};
|
|
75
87
|
/**
|
|
76
|
-
* Resolve the
|
|
88
|
+
* Resolve the CRUD affordances a UI surface should expose for an object —
|
|
89
|
+
* the spec's object-level matrix, INTERSECTED with what the server will
|
|
90
|
+
* actually accept.
|
|
91
|
+
*
|
|
92
|
+
* The bucket/`userActions` half is **not computed here**: it is delegated to
|
|
93
|
+
* `resolveCrudAffordances()` from `@objectstack/spec/data` (ADR-0103), so the
|
|
94
|
+
* bucket table has exactly one definition on the platform. Only the
|
|
95
|
+
* intersection below is objectui's, which is why this function no longer
|
|
96
|
+
* carries the spec's name (objectstack#4115).
|
|
77
97
|
*
|
|
78
98
|
* @param obj The object schema (managedBy bucket + userActions overrides).
|
|
79
99
|
* @param effectiveApiOperations OPTIONAL server-resolved effective API operation
|
|
@@ -84,23 +104,15 @@ const AFFORDANCE_TO_API_OPERATION = {
|
|
|
84
104
|
* (old backend / no effective set) leaves the affordances untouched —
|
|
85
105
|
* backward-compatible. An empty array means "expose nothing" → all bits off.
|
|
86
106
|
*/
|
|
87
|
-
export function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
edit: edit.enabled,
|
|
97
|
-
delete: del.enabled,
|
|
98
|
-
exportCsv: o.exportCsv ?? base.exportCsv,
|
|
99
|
-
};
|
|
100
|
-
if (edit.predicates)
|
|
101
|
-
out.editPredicates = edit.predicates;
|
|
102
|
-
if (del.predicates)
|
|
103
|
-
out.deletePredicates = del.predicates;
|
|
107
|
+
export function resolveEffectiveCrudAffordances(obj, effectiveApiOperations) {
|
|
108
|
+
// The spec's resolver is the single definition of the bucket defaults and the
|
|
109
|
+
// `userActions` override parse (including the #2614 object form). Its input
|
|
110
|
+
// type does not model the `null`s objectui's loosely-typed schemas carry, so
|
|
111
|
+
// they are normalised away at this one seam rather than re-deriving anything.
|
|
112
|
+
const out = resolveSpecCrudAffordances({
|
|
113
|
+
managedBy: obj?.managedBy ?? undefined,
|
|
114
|
+
userActions: (obj?.userActions ?? undefined),
|
|
115
|
+
});
|
|
104
116
|
// [#3391] Intersect with the server's effective API operations when present.
|
|
105
117
|
// The frontend consumes the effective set the server hands down; it never
|
|
106
118
|
// reads the raw `apiMethods` nor re-derives.
|
|
@@ -118,15 +130,26 @@ export function isWriteOptedIn(v) {
|
|
|
118
130
|
return v === true || (typeof v === 'object' && v !== null && v.enabled === true);
|
|
119
131
|
}
|
|
120
132
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
133
|
+
* True for a platform-defined schema whose DATA is admin/user-writable — the
|
|
134
|
+
* `system-data` bucket. Used to pick the writable-system badge/empty-state copy
|
|
135
|
+
* instead of the engine-owned "read-only monitoring surface" copy.
|
|
136
|
+
*
|
|
137
|
+
* **Simplified in protocol 17 (objectstack#3355).** Under ADR-0103's v16 split
|
|
138
|
+
* this had to RECOVER the distinction from `userActions`: `system` doubled as
|
|
139
|
+
* both the engine-owned default and the writable set, so "did it open any
|
|
140
|
+
* write?" was the only way to tell a Notification Preferences grid from an
|
|
141
|
+
* automation-run log. v17 retired that overload — the writable half is now the
|
|
142
|
+
* `system-data` bucket (writable by DEFAULT) and the engine-owned half is
|
|
143
|
+
* `engine-owned` — so the bucket answers the question directly and the
|
|
144
|
+
* `userActions` probe would only re-derive what the value already states.
|
|
145
|
+
*
|
|
146
|
+
* The narrowing case is deliberately still `true`: a `system-data` grid that
|
|
147
|
+
* narrows to edit-only is still platform-schema/user-data and should read as
|
|
148
|
+
* such. The spec refuses `system-data` on an object that narrows away every
|
|
149
|
+
* write, so there is no "writable bucket with no writes" shape to worry about.
|
|
124
150
|
*/
|
|
125
151
|
export function isSystemWritable(obj) {
|
|
126
|
-
|
|
127
|
-
return false;
|
|
128
|
-
const o = obj?.userActions ?? {};
|
|
129
|
-
return o.create === true || isWriteOptedIn(o.edit) || isWriteOptedIn(o.delete);
|
|
152
|
+
return obj?.managedBy === 'system-data';
|
|
130
153
|
}
|
|
131
154
|
/**
|
|
132
155
|
* Whether an object's rows may be edited in place (detail inline-edit / form
|
|
@@ -143,5 +166,5 @@ export function isSystemWritable(obj) {
|
|
|
143
166
|
* bucket affordance untouched.
|
|
144
167
|
*/
|
|
145
168
|
export function isObjectInlineEditable(obj, effectiveApiOperations) {
|
|
146
|
-
return
|
|
169
|
+
return resolveEffectiveCrudAffordances(obj, effectiveApiOperations).edit;
|
|
147
170
|
}
|
|
@@ -5,10 +5,20 @@
|
|
|
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 { RowHeight } from '@objectstack/spec/ui';
|
|
8
9
|
/** ListView's toolbar density vocabulary — three steps, not the spec's five. */
|
|
9
10
|
export type DensityMode = 'compact' | 'comfortable' | 'spacious';
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The spec's `RowHeightSchema` vocabulary — **re-exported, not re-declared**.
|
|
13
|
+
*
|
|
14
|
+
* The hand-written union that used to sit here already advertised itself as
|
|
15
|
+
* "the spec's `RowHeightSchema` vocabulary" in its doc comment, which is the
|
|
16
|
+
* failure class objectstack#4115 is about: the claim was true when written and
|
|
17
|
+
* nothing would have caught the day it stopped being true. The maps below are
|
|
18
|
+
* `Record<RowHeight, …>`, so a spec-side addition now fails the build here
|
|
19
|
+
* instead of silently leaving a row height with no density mapping.
|
|
20
|
+
*/
|
|
21
|
+
export type { RowHeight };
|
|
12
22
|
/**
|
|
13
23
|
* `densityMode` → `rowHeight`. The two vocabularies are not the same size: the
|
|
14
24
|
* spec has five row heights, ListView's toolbar offers three. Widening maps
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* validation (`schema-validator.js`, `validation-engine.js`) is unaffected: it
|
|
13
13
|
* is client-side form validation, not a mirror of a server rule.
|
|
14
14
|
*/
|
|
15
|
+
export * from './required-presence.js';
|
|
15
16
|
export * from './validation-engine.js';
|
|
16
17
|
export * from './schema-validator.js';
|
|
17
18
|
export * from './validators/index.js';
|
package/dist/validation/index.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* validation (`schema-validator.js`, `validation-engine.js`) is unaffected: it
|
|
13
13
|
* is client-side form validation, not a mirror of a server rule.
|
|
14
14
|
*/
|
|
15
|
+
export * from './required-presence.js';
|
|
15
16
|
export * from './validation-engine.js';
|
|
16
17
|
export * from './schema-validator.js';
|
|
17
18
|
export * from './validators/index.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The ONE client-side answer to "is this field's value absent?" for a
|
|
10
|
+
* `required` check.
|
|
11
|
+
*
|
|
12
|
+
* `required` is a PRESENCE contract, not a truthiness one. `@objectstack/spec`
|
|
13
|
+
* FieldSchema.required (ADR-0113) reads "an insert must provide a NON-NULL
|
|
14
|
+
* value", and objectql's record validator implements exactly that — its
|
|
15
|
+
* `isMissing` is `undefined | null | blank string`, nothing more. This mirrors
|
|
16
|
+
* that verdict so the form, the wizard's cross-step gate, and the server all
|
|
17
|
+
* agree instead of each inventing a dialect.
|
|
18
|
+
*
|
|
19
|
+
* What this deliberately does NOT treat as absent:
|
|
20
|
+
* - `false` — a boolean's second value. react-hook-form's own `required` rule
|
|
21
|
+
* fails on `isBoolean(v) && !v`, which quietly turns a required checkbox into
|
|
22
|
+
* "must be TRUE": an AI-built task tracker could not create an UNFINISHED
|
|
23
|
+
* task, because the state the switch shows by default was the one value it
|
|
24
|
+
* refused to save (cloud#972).
|
|
25
|
+
* - `0` — a number's identity element. "Zero hours logged" is data.
|
|
26
|
+
*
|
|
27
|
+
* An empty ARRAY *is* absent: the server has no array-valued scalar to
|
|
28
|
+
* disagree about, and "nothing picked" is a multiselect's own empty state,
|
|
29
|
+
* exactly as a blank string is a text input's.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isMissingForRequired(value: unknown): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The ONE client-side answer to "is this field's value absent?" for a
|
|
10
|
+
* `required` check.
|
|
11
|
+
*
|
|
12
|
+
* `required` is a PRESENCE contract, not a truthiness one. `@objectstack/spec`
|
|
13
|
+
* FieldSchema.required (ADR-0113) reads "an insert must provide a NON-NULL
|
|
14
|
+
* value", and objectql's record validator implements exactly that — its
|
|
15
|
+
* `isMissing` is `undefined | null | blank string`, nothing more. This mirrors
|
|
16
|
+
* that verdict so the form, the wizard's cross-step gate, and the server all
|
|
17
|
+
* agree instead of each inventing a dialect.
|
|
18
|
+
*
|
|
19
|
+
* What this deliberately does NOT treat as absent:
|
|
20
|
+
* - `false` — a boolean's second value. react-hook-form's own `required` rule
|
|
21
|
+
* fails on `isBoolean(v) && !v`, which quietly turns a required checkbox into
|
|
22
|
+
* "must be TRUE": an AI-built task tracker could not create an UNFINISHED
|
|
23
|
+
* task, because the state the switch shows by default was the one value it
|
|
24
|
+
* refused to save (cloud#972).
|
|
25
|
+
* - `0` — a number's identity element. "Zero hours logged" is data.
|
|
26
|
+
*
|
|
27
|
+
* An empty ARRAY *is* absent: the server has no array-valued scalar to
|
|
28
|
+
* disagree about, and "nothing picked" is a multiselect's own empty state,
|
|
29
|
+
* exactly as a blank string is a text input's.
|
|
30
|
+
*/
|
|
31
|
+
export function isMissingForRequired(value) {
|
|
32
|
+
return (value === undefined ||
|
|
33
|
+
value === null ||
|
|
34
|
+
(typeof value === 'string' && value.trim() === '') ||
|
|
35
|
+
(Array.isArray(value) && value.length === 0));
|
|
36
|
+
}
|
|
@@ -16,21 +16,37 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { BaseSchema } from '@object-ui/types';
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* One issue found while walking an ObjectUI schema TREE — `path` locates the
|
|
20
|
+
* offending node (e.g. `children[0].type`), and `type` says whether it is fatal.
|
|
21
|
+
*
|
|
22
|
+
* NOT the spec's `ValidationError` (`@objectstack/spec/kernel`), whose name this
|
|
23
|
+
* interface carried until objectstack#4115. That one describes PLUGIN-manifest
|
|
24
|
+
* validation and is keyed by `field`, with no severity discriminant. The two
|
|
25
|
+
* share no key beyond `message`/`code`.
|
|
26
|
+
*
|
|
27
|
+
* Naming convention for this family, set here and followed by the rest of the
|
|
28
|
+
* burn-down: `<what-is-validated>Validation<Error|Result>`. (`SchemaValidationResult`
|
|
29
|
+
* and `SchemaValidationReport` are themselves spec exports — checked, not assumed.)
|
|
20
30
|
*/
|
|
21
|
-
export interface
|
|
31
|
+
export interface SchemaNodeValidationError {
|
|
22
32
|
path: string;
|
|
23
33
|
message: string;
|
|
24
34
|
type: 'error' | 'warning';
|
|
25
35
|
code?: string;
|
|
26
36
|
}
|
|
27
37
|
/**
|
|
28
|
-
*
|
|
38
|
+
* The outcome of validating an ObjectUI schema tree.
|
|
39
|
+
*
|
|
40
|
+
* NOT the spec's `ValidationResult` (`@objectstack/spec/kernel` and
|
|
41
|
+
* `@objectstack/spec/contracts`), whose name this interface carried until
|
|
42
|
+
* objectstack#4115: there `errors`/`warnings` are OPTIONAL arrays of
|
|
43
|
+
* `{ field, message, code? }` describing a plugin manifest. Here both arrays are
|
|
44
|
+
* required and carry node paths.
|
|
29
45
|
*/
|
|
30
|
-
export interface
|
|
46
|
+
export interface SchemaNodeValidationResult {
|
|
31
47
|
valid: boolean;
|
|
32
|
-
errors:
|
|
33
|
-
warnings:
|
|
48
|
+
errors: SchemaNodeValidationError[];
|
|
49
|
+
warnings: SchemaNodeValidationError[];
|
|
34
50
|
}
|
|
35
51
|
/**
|
|
36
52
|
* Validate a complete schema
|
|
@@ -53,7 +69,7 @@ export interface ValidationResult {
|
|
|
53
69
|
* }
|
|
54
70
|
* ```
|
|
55
71
|
*/
|
|
56
|
-
export declare function validateSchema(schema: any, path?: string):
|
|
72
|
+
export declare function validateSchema(schema: any, path?: string): SchemaNodeValidationResult;
|
|
57
73
|
/**
|
|
58
74
|
* Assert that a schema is valid, throwing an error if not
|
|
59
75
|
*
|
|
@@ -91,4 +107,4 @@ export declare function isValidSchema(value: any): value is BaseSchema;
|
|
|
91
107
|
* @param result - The validation result
|
|
92
108
|
* @returns Formatted error summary
|
|
93
109
|
*/
|
|
94
|
-
export declare function formatValidationErrors(result:
|
|
110
|
+
export declare function formatValidationErrors(result: SchemaNodeValidationResult): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/core",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@objectstack/formula": "^17.0.0-rc.
|
|
29
|
-
"@objectstack/spec": "^17.0.0-rc.
|
|
28
|
+
"@objectstack/formula": "^17.0.0-rc.1",
|
|
29
|
+
"@objectstack/spec": "^17.0.0-rc.1",
|
|
30
30
|
"lodash": "^4.18.1",
|
|
31
31
|
"zod": "^4.4.3",
|
|
32
|
-
"@object-ui/types": "17.
|
|
32
|
+
"@object-ui/types": "17.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"typescript": "^6.0.3",
|