@itwin/core-backend 5.14.0-dev.21 → 5.14.0-dev.23

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 (67) hide show
  1. package/lib/cjs/ECDb.d.ts +11 -6
  2. package/lib/cjs/ECDb.d.ts.map +1 -1
  3. package/lib/cjs/ECDb.js +11 -6
  4. package/lib/cjs/ECDb.js.map +1 -1
  5. package/lib/cjs/ECSqlStatement.d.ts +2 -2
  6. package/lib/cjs/ECSqlStatement.js +2 -2
  7. package/lib/cjs/ECSqlStatement.js.map +1 -1
  8. package/lib/cjs/IModelDb.d.ts +12 -10
  9. package/lib/cjs/IModelDb.d.ts.map +1 -1
  10. package/lib/cjs/IModelDb.js +19 -12
  11. package/lib/cjs/IModelDb.js.map +1 -1
  12. package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts +90 -5
  13. package/lib/cjs/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -1
  14. package/lib/cjs/annotations/ElementDrivesTextAnnotation.js +59 -1
  15. package/lib/cjs/annotations/ElementDrivesTextAnnotation.js.map +1 -1
  16. package/lib/cjs/internal/annotations/fieldSpecs.d.ts +82 -0
  17. package/lib/cjs/internal/annotations/fieldSpecs.d.ts.map +1 -0
  18. package/lib/cjs/internal/annotations/fieldSpecs.js +187 -0
  19. package/lib/cjs/internal/annotations/fieldSpecs.js.map +1 -0
  20. package/lib/cjs/internal/annotations/fields.d.ts +39 -2
  21. package/lib/cjs/internal/annotations/fields.d.ts.map +1 -1
  22. package/lib/cjs/internal/annotations/fields.js +140 -13
  23. package/lib/cjs/internal/annotations/fields.js.map +1 -1
  24. package/lib/esm/ECDb.d.ts +11 -6
  25. package/lib/esm/ECDb.d.ts.map +1 -1
  26. package/lib/esm/ECDb.js +11 -6
  27. package/lib/esm/ECDb.js.map +1 -1
  28. package/lib/esm/ECSqlStatement.d.ts +2 -2
  29. package/lib/esm/ECSqlStatement.js +2 -2
  30. package/lib/esm/ECSqlStatement.js.map +1 -1
  31. package/lib/esm/IModelDb.d.ts +12 -10
  32. package/lib/esm/IModelDb.d.ts.map +1 -1
  33. package/lib/esm/IModelDb.js +19 -12
  34. package/lib/esm/IModelDb.js.map +1 -1
  35. package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts +90 -5
  36. package/lib/esm/annotations/ElementDrivesTextAnnotation.d.ts.map +1 -1
  37. package/lib/esm/annotations/ElementDrivesTextAnnotation.js +60 -2
  38. package/lib/esm/annotations/ElementDrivesTextAnnotation.js.map +1 -1
  39. package/lib/esm/internal/annotations/fieldSpecs.d.ts +82 -0
  40. package/lib/esm/internal/annotations/fieldSpecs.d.ts.map +1 -0
  41. package/lib/esm/internal/annotations/fieldSpecs.js +179 -0
  42. package/lib/esm/internal/annotations/fieldSpecs.js.map +1 -0
  43. package/lib/esm/internal/annotations/fields.d.ts +39 -2
  44. package/lib/esm/internal/annotations/fields.d.ts.map +1 -1
  45. package/lib/esm/internal/annotations/fields.js +140 -13
  46. package/lib/esm/internal/annotations/fields.js.map +1 -1
  47. package/lib/esm/test/AnnotationTestUtils.d.ts +9 -0
  48. package/lib/esm/test/AnnotationTestUtils.d.ts.map +1 -1
  49. package/lib/esm/test/AnnotationTestUtils.js +17 -0
  50. package/lib/esm/test/AnnotationTestUtils.js.map +1 -1
  51. package/lib/esm/test/annotations/FieldFormat.test.d.ts +2 -0
  52. package/lib/esm/test/annotations/FieldFormat.test.d.ts.map +1 -0
  53. package/lib/esm/test/annotations/FieldFormat.test.js +738 -0
  54. package/lib/esm/test/annotations/FieldFormat.test.js.map +1 -0
  55. package/lib/esm/test/annotations/FieldFormattingLifecycle.test.d.ts +2 -0
  56. package/lib/esm/test/annotations/FieldFormattingLifecycle.test.d.ts.map +1 -0
  57. package/lib/esm/test/annotations/FieldFormattingLifecycle.test.js +323 -0
  58. package/lib/esm/test/annotations/FieldFormattingLifecycle.test.js.map +1 -0
  59. package/lib/esm/test/annotations/FieldSpecs.test.d.ts +2 -0
  60. package/lib/esm/test/annotations/FieldSpecs.test.d.ts.map +1 -0
  61. package/lib/esm/test/annotations/FieldSpecs.test.js +54 -0
  62. package/lib/esm/test/annotations/FieldSpecs.test.js.map +1 -0
  63. package/lib/esm/test/annotations/Fields.test.js +221 -27
  64. package/lib/esm/test/annotations/Fields.test.js.map +1 -1
  65. package/lib/esm/test/ecdb/QueryReaders.test.js +105 -30
  66. package/lib/esm/test/ecdb/QueryReaders.test.js.map +1 -1
  67. package/package.json +15 -13
@@ -1,13 +1,44 @@
1
1
  /** @packageDocumentation
2
2
  * @module Elements
3
3
  */
4
- import { Id64String } from "@itwin/core-bentley";
4
+ import { BeEvent, Id64String } from "@itwin/core-bentley";
5
5
  import { RelatedElement, TextBlock } from "@itwin/core-common";
6
+ import { UnitSystemKey } from "@itwin/core-quantity";
7
+ import { FormatSet } from "@itwin/ecschema-metadata";
6
8
  import { Element } from "../Element";
7
9
  import { IModelDb } from "../IModelDb";
8
10
  import { IModelElementCloneContext } from "../IModelElementCloneContext";
9
11
  import { ElementDrivesElement, OnDependencyArg } from "../Relationship";
10
12
  import { EditTxn } from "../EditTxn";
13
+ /** Configures how `"quantity"` and `"coordinate"` [FieldRun]($common)s in an iModel are
14
+ * formatted. Passed to [[ElementDrivesTextAnnotation.registerFieldFormatting]].
15
+ * @beta
16
+ */
17
+ export interface FieldFormattingArgs {
18
+ /** The iModel whose annotations this configuration formats. Its `schemaContext` supplies the
19
+ * KindOfQuantity presentation formats that every FormatSet falls back to.
20
+ */
21
+ iModel: IModelDb;
22
+ /** The FormatSet adopted for this iModel. It applies to every [FieldRun]($common) that does
23
+ * not name a different one via [QuantityFieldFormatOptions.formatSet]($common), and takes
24
+ * precedence over the schema's own presentation formats.
25
+ */
26
+ formatSet?: FormatSet;
27
+ /** Additional FormatSets addressable per-field, each paired with the id that
28
+ * [FieldRun]($common)s reference via [QuantityFieldFormatOptions.formatSet]($common).
29
+ * The id must be unique; if two entries share an id the last one wins. A field naming an id
30
+ * absent from this list falls back to [[formatSet]].
31
+ */
32
+ formatSets?: ReadonlyArray<{
33
+ id: string;
34
+ formatSet: FormatSet;
35
+ }>;
36
+ /** Unit system used to pick a KindOfQuantity's presentation format when the schema offers
37
+ * several. Defaults to [[formatSet]]'s own `unitSystem`, or `"metric"` when no FormatSet is
38
+ * adopted.
39
+ */
40
+ unitSystem?: UnitSystemKey;
41
+ }
11
42
  /** Describes one of potentially many [TextBlock]($common)s hosted by an [[ITextAnnotation]].
12
43
  * For example, a [[TextAnnotation2d]] hosts only a single text block, but an element representing a table may
13
44
  * host one text block for each cell in the table, in which case it might use the combination of row and column
@@ -66,9 +97,7 @@ export declare class ElementDrivesTextAnnotation extends ElementDrivesElement {
66
97
  * update when the source element changes.
67
98
  */
68
99
  static isSupportedForIModel(iModel: IModelDb): boolean;
69
- /** Examines all of the [FieldRun]($common)s within the specified [[ITextAnnotation]] and ensures that the appropriate
70
- * `ElementDrivesTextAnnotation` relationships exist between the fields' source elements and this target element.
71
- * It also deletes any stale relationships left over from fields that were deleted or whose source elements changed.
100
+ /** Ensures the `ElementDrivesTextAnnotation` relationships for the [FieldRun]($common)s in the specified annotation are up to date.
72
101
  * @deprecated in 5.9.0 - will not be removed until after 2027-05-04. Use ElementDrivesTextAnnotation.updateFieldDependencies(txn, ...) instead.
73
102
  */
74
103
  static updateFieldDependencies(annotationElementId: Id64String, iModel: IModelDb): void;
@@ -78,10 +107,66 @@ export declare class ElementDrivesTextAnnotation extends ElementDrivesElement {
78
107
  */
79
108
  static updateFieldDependencies(txn: EditTxn, annotationElementId: Id64String): void;
80
109
  /** Recompute the display strings of all [FieldRun]($common)s in a [TextBlock]($common).
110
+ *
111
+ * `"quantity"` and `"coordinate"` fields are formatted through the FormatSets configured for
112
+ * `args.iModel` by [[registerFieldFormatting]], or otherwise through the format each
113
+ * KindOfQuantity's schema declares.
114
+ *
115
+ * A field whose format cannot be resolved falls back to `value.toString()` and is logged. A
116
+ * field whose property cannot be resolved, or whose format throws, is logged and rendered as
117
+ * [FieldRun.invalidContentIndicator]($common); one bad field does not abandon the rest of the
118
+ * block.
81
119
  * @returns the number of fields whose display strings were modified.
82
- * @throws Error if evaluation of any field fails.
83
120
  */
84
121
  static evaluateFields(args: EvaluateFieldsArgs): number;
122
+ /** Configures how `"quantity"` and `"coordinate"` [FieldRun]($common)s in `args.iModel` are
123
+ * formatted by [[evaluateFields]] and by `TxnManager` field-update callbacks.
124
+ *
125
+ * Calling this is optional. An iModel with no registration formats every field using the
126
+ * presentation format its schema declares for the KindOfQuantity, in the metric unit system.
127
+ * Register to layer application [FormatSet]($ecschema-metadata)s over those schema defaults,
128
+ * or to select a different unit system. Formats resolve in this order:
129
+ *
130
+ * 1. The FormatSet named by the field's [QuantityFieldFormatOptions.formatSet]($common).
131
+ * 2. The FormatSet adopted for the iModel ([[FieldFormattingArgs.formatSet]]).
132
+ * 3. The KindOfQuantity's presentation format for [[FieldFormattingArgs.unitSystem]].
133
+ * 4. `value.toString()`, with a warning logged.
134
+ *
135
+ * Units resolve through the bundled BIS [BasicUnitsProvider]($core-quantity) only. A field
136
+ * whose persistence unit, or whose format's units, are defined solely by the iModel's own
137
+ * schemas falls to step 4.
138
+ *
139
+ * Pass the iModel-wide default as `formatSet` and any per-field alternatives as `formatSets`,
140
+ * keyed by the id that [FieldRun]($common)s name via
141
+ * [QuantityFieldFormatOptions.formatSet]($common):
142
+ *
143
+ * ```ts
144
+ * ElementDrivesTextAnnotation.registerFieldFormatting({
145
+ * iModel,
146
+ * formatSet: metricFormatSet,
147
+ * formatSets: [{ id: imperialFormatSetId, formatSet: imperial }],
148
+ * });
149
+ * ```
150
+ *
151
+ * Each call replaces any prior registration for the same iModel; calling it with only
152
+ * `iModel` reverts to the schema defaults. Registering does not re-evaluate existing
153
+ * annotations; listen to [[onFieldFormattingChanged]] to do so. The registration lives as long
154
+ * as the `IModelDb` object and is released with it.
155
+ * @see [Quantity formatting for text annotation fields]($docs/learning/backend/TextAnnotationFields.md)
156
+ * @beta
157
+ */
158
+ static registerFieldFormatting(args: FieldFormattingArgs): void;
159
+ /** Raised after every [[registerFieldFormatting]] call, once the new configuration is in
160
+ * place, whether or not it differs from the previous one. Applications that cache formatted
161
+ * output, or that want to re-evaluate existing annotations against a newly adopted FormatSet,
162
+ * should listen here. A listener that throws does not undo the registration; its error is
163
+ * reported through [UnexpectedErrors]($bentley) rather than to the caller of
164
+ * `registerFieldFormatting`.
165
+ * @beta
166
+ */
167
+ static readonly onFieldFormattingChanged: BeEvent<(args: {
168
+ iModel: IModelDb;
169
+ }) => void>;
85
170
  /** When copying an [[ITextAnnotation]] from one iModel into another, remaps the element Ids in any [FieldPropertyHost]($common) within the cloned element
86
171
  * so that they refer to elements in the `context`'s target iModel, and sets any Ids that cannot be remapped to [Id64.invalid]($bentley).
87
172
  * Implementations of `ITextAnnotation` should invoke this function from their implementations of [[Element.onCloned]].
@@ -1 +1 @@
1
- {"version":3,"file":"ElementDrivesTextAnnotation.d.ts","sourceRoot":"","sources":["../../../src/annotations/ElementDrivesTextAnnotation.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,OAAO,EAAQ,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAe,cAAc,EAAE,SAAS,EAA8B,MAAM,oBAAoB,CAAC;AAExG,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAGzE,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,sBAAsB;IACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,gIAAgI;IAChI,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;CACtB;AAKD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,oCAAoC,CAAC;IACxD,sFAAsF;IACtF,aAAa,IAAI,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC1C,mGAAmG;IACnG,gBAAgB,CAAC,UAAU,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;CACtD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,eAAe,GAAG,OAAO,CAExF;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,KAAK,EAAE,SAAS,CAAC;IACjB,oGAAoG;IACpG,MAAM,EAAE,QAAQ,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,qBAAa,2BAA4B,SAAQ,oBAAoB;IACnE,WAA2B,SAAS,IAAI,MAAM,CAA0C;IAExF,OAAO,CAAC,MAAM,CAAC,2BAA2B;IAuE1C,gBAAgB;WACO,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAInE,gBAAgB;WACO,sBAAsB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI;IAIzE;;;OAGG;WACW,oBAAoB,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO;IAI7D;;;;OAIG;WACW,uBAAuB,CAAC,mBAAmB,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,GAAG,IAAI;IAE9F;;;OAGG;WACW,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,mBAAmB,EAAE,UAAU,GAAG,IAAI;IAU1F;;;OAGG;WACW,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM;IAI9D;;;OAGG;WACW,WAAW,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,yBAAyB,GAAG,IAAI;CAwB5F;AAED;;GAEG;AACH,qBAAa,oCAAqC,SAAQ,cAAc;IACtE,OAAc,aAAa,SAAkD;gBAC1D,qBAAqB,EAAE,UAAU,EAAE,YAAY,GAAE,MAA2D;CAGhI"}
1
+ {"version":3,"file":"ElementDrivesTextAnnotation.d.ts","sourceRoot":"","sources":["../../../src/annotations/ElementDrivesTextAnnotation.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ const core_bentley_1 = require("@itwin/core-bentley");
13
13
  const core_common_1 = require("@itwin/core-common");
14
14
  const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
15
15
  const fields_1 = require("../internal/annotations/fields");
16
+ const fieldSpecs_1 = require("../internal/annotations/fieldSpecs");
16
17
  const Symbols_1 = require("../internal/Symbols");
17
18
  const Relationship_1 = require("../Relationship");
18
19
  const EditTxn_1 = require("../EditTxn");
@@ -114,12 +115,69 @@ class ElementDrivesTextAnnotation extends Relationship_1.ElementDrivesElement {
114
115
  this.updateFieldDependenciesImpl(arg2[Symbols_1._implicitTxn], arg1);
115
116
  }
116
117
  /** Recompute the display strings of all [FieldRun]($common)s in a [TextBlock]($common).
118
+ *
119
+ * `"quantity"` and `"coordinate"` fields are formatted through the FormatSets configured for
120
+ * `args.iModel` by [[registerFieldFormatting]], or otherwise through the format each
121
+ * KindOfQuantity's schema declares.
122
+ *
123
+ * A field whose format cannot be resolved falls back to `value.toString()` and is logged. A
124
+ * field whose property cannot be resolved, or whose format throws, is logged and rendered as
125
+ * [FieldRun.invalidContentIndicator]($common); one bad field does not abandon the rest of the
126
+ * block.
117
127
  * @returns the number of fields whose display strings were modified.
118
- * @throws Error if evaluation of any field fails.
119
128
  */
120
129
  static evaluateFields(args) {
121
130
  return (0, fields_1.updateFields)(args.block, (0, fields_1.createUpdateContext)(undefined, args.iModel, false));
122
131
  }
132
+ /** Configures how `"quantity"` and `"coordinate"` [FieldRun]($common)s in `args.iModel` are
133
+ * formatted by [[evaluateFields]] and by `TxnManager` field-update callbacks.
134
+ *
135
+ * Calling this is optional. An iModel with no registration formats every field using the
136
+ * presentation format its schema declares for the KindOfQuantity, in the metric unit system.
137
+ * Register to layer application [FormatSet]($ecschema-metadata)s over those schema defaults,
138
+ * or to select a different unit system. Formats resolve in this order:
139
+ *
140
+ * 1. The FormatSet named by the field's [QuantityFieldFormatOptions.formatSet]($common).
141
+ * 2. The FormatSet adopted for the iModel ([[FieldFormattingArgs.formatSet]]).
142
+ * 3. The KindOfQuantity's presentation format for [[FieldFormattingArgs.unitSystem]].
143
+ * 4. `value.toString()`, with a warning logged.
144
+ *
145
+ * Units resolve through the bundled BIS [BasicUnitsProvider]($core-quantity) only. A field
146
+ * whose persistence unit, or whose format's units, are defined solely by the iModel's own
147
+ * schemas falls to step 4.
148
+ *
149
+ * Pass the iModel-wide default as `formatSet` and any per-field alternatives as `formatSets`,
150
+ * keyed by the id that [FieldRun]($common)s name via
151
+ * [QuantityFieldFormatOptions.formatSet]($common):
152
+ *
153
+ * ```ts
154
+ * ElementDrivesTextAnnotation.registerFieldFormatting({
155
+ * iModel,
156
+ * formatSet: metricFormatSet,
157
+ * formatSets: [{ id: imperialFormatSetId, formatSet: imperial }],
158
+ * });
159
+ * ```
160
+ *
161
+ * Each call replaces any prior registration for the same iModel; calling it with only
162
+ * `iModel` reverts to the schema defaults. Registering does not re-evaluate existing
163
+ * annotations; listen to [[onFieldFormattingChanged]] to do so. The registration lives as long
164
+ * as the `IModelDb` object and is released with it.
165
+ * @see [Quantity formatting for text annotation fields]($docs/learning/backend/TextAnnotationFields.md)
166
+ * @beta
167
+ */
168
+ static registerFieldFormatting(args) {
169
+ (0, fieldSpecs_1.setFieldFormatting)(args.iModel, (0, fieldSpecs_1.createFieldFormatting)(args));
170
+ this.onFieldFormattingChanged.raiseEvent({ iModel: args.iModel });
171
+ }
172
+ /** Raised after every [[registerFieldFormatting]] call, once the new configuration is in
173
+ * place, whether or not it differs from the previous one. Applications that cache formatted
174
+ * output, or that want to re-evaluate existing annotations against a newly adopted FormatSet,
175
+ * should listen here. A listener that throws does not undo the registration; its error is
176
+ * reported through [UnexpectedErrors]($bentley) rather than to the caller of
177
+ * `registerFieldFormatting`.
178
+ * @beta
179
+ */
180
+ static onFieldFormattingChanged = new core_bentley_1.BeEvent();
123
181
  /** When copying an [[ITextAnnotation]] from one iModel into another, remaps the element Ids in any [FieldPropertyHost]($common) within the cloned element
124
182
  * so that they refer to elements in the `context`'s target iModel, and sets any Ids that cannot be remapped to [Id64.invalid]($bentley).
125
183
  * Implementations of `ITextAnnotation` should invoke this function from their implementations of [[Element.onCloned]].
@@ -1 +1 @@
1
- {"version":3,"file":"ElementDrivesTextAnnotation.js","sourceRoot":"","sources":["../../../src/annotations/ElementDrivesTextAnnotation.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n/** @packageDocumentation\r\n * @module Elements\r\n */\r\n\r\nimport { Id64, Id64String } from \"@itwin/core-bentley\";\r\nimport { QueryBinder, RelatedElement, TextBlock, traverseTextBlockComponent } from \"@itwin/core-common\";\r\nimport { ECVersion } from \"@itwin/ecschema-metadata\";\r\nimport { Element } from \"../Element\";\r\nimport { IModelDb } from \"../IModelDb\";\r\nimport { IModelElementCloneContext } from \"../IModelElementCloneContext\";\r\nimport { createUpdateContext, updateAllFields, updateElementFields, updateFields } from \"../internal/annotations/fields\";\r\nimport { _implicitTxn } from \"../internal/Symbols\";\r\nimport { ElementDrivesElement, OnDependencyArg } from \"../Relationship\";\r\nimport { EditTxn } from \"../EditTxn\";\r\n\r\n/** Describes one of potentially many [TextBlock]($common)s hosted by an [[ITextAnnotation]].\r\n * For example, a [[TextAnnotation2d]] hosts only a single text block, but an element representing a table may\r\n * host one text block for each cell in the table, in which case it might use the combination of row and column\r\n * as the [[id]] for each text block.\r\n * @beta\r\n */\r\nexport interface TextBlockAndId {\r\n /** The text block. */\r\n readonly textBlock: TextBlock;\r\n /** An opaque identifier that allows the [[ITextAnnotation]] to discern which of its hosted text blocks is being referred to. */\r\n readonly id: unknown;\r\n}\r\n\r\n// ElementDrivesTextAnnotation was introduced in this version of BisCore - iModels with earlier versions cannot support field dependencies.\r\nconst minBisCoreVersion = new ECVersion(1, 0, 22);\r\n\r\n/** Interface implemented by [[GeometricElement]] subclasses whose schemas declare them to implement the mix-in `BisCore:ITextAnnotation`.\r\n * Such elements may host any number of [TextBlock]($common)s. `ITextAnnotation` provides a uniform way to interact with text regardless of\r\n * the type of element to which it belongs.\r\n * @beta\r\n */\r\nexport interface ITextAnnotation {\r\n /** The default [[AnnotationTextStyle]] used by the text annotation. */\r\n defaultTextStyle?: TextAnnotationUsesTextStyleByDefault;\r\n /** Obtain a collection of all of the [TextBlock]($common)s hosted by this element. */\r\n getTextBlocks(): Iterable<TextBlockAndId>;\r\n /** Update the element in-memory to replace the contents of the specified [TextBlock]($common)s. */\r\n updateTextBlocks(textBlocks: TextBlockAndId[]): void;\r\n}\r\n\r\n/** Returns `true` if the specified `element` implements [[ITextAnnotation]].\r\n * @beta\r\n */\r\nexport function isITextAnnotation(element: Element): element is ITextAnnotation & Element {\r\n return [\"getTextBlocks\", \"updateTextBlocks\"].every((x) => x in element && typeof (element as any)[x] === \"function\");\r\n}\r\n\r\n/** Arguments supplied to [[ElementDrivesTextAnnotation.evaluateFields]].\r\n * @beta\r\n */\r\nexport interface EvaluateFieldsArgs {\r\n /** The text block whose fields are to be evaluated. */\r\n block: TextBlock;\r\n /** The iModel containing the elements supplying the display strings for the fields in [[block]]. */\r\n iModel: IModelDb;\r\n}\r\n\r\n/** A relationship in which the source element hosts one or more properties that are displayed by a target [[ITextAnnotation]] element.\r\n * This relationship is used to automatically update the [FieldRun]($common)s contained in the target element when the source element is modified.\r\n * An [[ITextAnnotation]] element should invoke [[updateFieldDependencies]] from its [[Element.onInserted]] and [[Element.onUpdated]] functions to\r\n * establish or update the relationships required for the [FieldRun]($common)s it contains.\r\n * @note This relationship was introduced in version 01.00.22 of the BisCore schema. [FieldRun]($common)s created in iModels that have not been upgraded to\r\n * that version or newer will not automatically update. Use [[isSupportedForIModel]] to check.\r\n * @beta\r\n */\r\nexport class ElementDrivesTextAnnotation extends ElementDrivesElement {\r\n public static override get className(): string { return \"ElementDrivesTextAnnotation\"; }\r\n\r\n private static updateFieldDependenciesImpl(txn: EditTxn, annotationElementId: Id64String): void {\r\n const iModel = txn.iModel;\r\n const annotationElement = iModel.elements.tryGetElement<Element>(annotationElementId);\r\n if (!annotationElement || !isITextAnnotation(annotationElement)) {\r\n return;\r\n }\r\n\r\n // The native layer will allow us to insert relationships to invalid or non-existent source elements...errors will arise later. Prevent it.\r\n function isValidSourceId(id: Id64String): boolean {\r\n if (!Id64.isValidId64(id)) {\r\n return false;\r\n }\r\n\r\n return iModel.withQueryReader(\"SELECT CodeValue FROM BisCore.Element WHERE ECInstanceId=?\", (reader): boolean => {\r\n return reader.step();\r\n }, new QueryBinder().bindId(1, id));\r\n }\r\n\r\n const sourceToRelationship = new Map<Id64String, Id64String | null>();\r\n const blocks = annotationElement.getTextBlocks();\r\n\r\n let haveFields = false;\r\n for (const block of blocks) {\r\n for (const { child } of traverseTextBlockComponent(block.textBlock)) {\r\n if (child.type === \"field\") {\r\n haveFields = true;\r\n if (isValidSourceId(child.propertyHost.elementId)) {\r\n sourceToRelationship.set(child.propertyHost.elementId, null);\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (haveFields) {\r\n iModel.requireMinimumSchemaVersion(\"BisCore\", minBisCoreVersion, \"Text fields\");\r\n updateAllFields(annotationElementId, txn);\r\n }\r\n\r\n const staleRelationships = new Set<Id64String>();\r\n if (this.isSupportedForIModel(iModel)) {\r\n annotationElement.iModel.withQueryReader(\r\n \"SELECT ECInstanceId, SourceECInstanceId FROM BisCore.ElementDrivesTextAnnotation WHERE TargetECInstanceId=:targetId\",\r\n (reader) => {\r\n for (const row of reader) {\r\n const relationshipId: Id64String = row[0];\r\n const sourceId: Id64String = row[1];\r\n if (sourceToRelationship.has(sourceId)) {\r\n sourceToRelationship.set(sourceId, relationshipId);\r\n } else {\r\n staleRelationships.add(relationshipId);\r\n }\r\n }\r\n },\r\n new QueryBinder().bindId(\"targetId\", annotationElement.id),\r\n );\r\n }\r\n\r\n for (const [sourceId, relationshipId] of sourceToRelationship) {\r\n if (relationshipId === null) {\r\n txn.insertRelationship(ElementDrivesTextAnnotation.create(annotationElement.iModel, sourceId, annotationElement.id).toJSON());\r\n }\r\n }\r\n\r\n if (staleRelationships.size > 0) {\r\n const staleRelationshipProps = Array.from(staleRelationships).map((relationshipId) =>\r\n annotationElement.iModel.relationships.getInstanceProps(\"BisCore.ElementDrivesTextAnnotation\", relationshipId)\r\n );\r\n txn.deleteRelationships(staleRelationshipProps);\r\n }\r\n }\r\n\r\n /** @internal */\r\n public static override onRootChangedArg(arg: OnDependencyArg): void {\r\n updateElementFields(arg.props, arg.indirectEditTxn, false);\r\n }\r\n\r\n /** @internal */\r\n public static override onDeletedDependencyArg(arg: OnDependencyArg): void {\r\n updateElementFields(arg.props, arg.indirectEditTxn, true);\r\n }\r\n\r\n /** Returns true if `iModel` contains a version of the BisCore schema new enough to support this relationship.\r\n * If not, the schema should be updated before inserting any [FieldRun]($common)s, or those runs will not\r\n * update when the source element changes.\r\n */\r\n public static isSupportedForIModel(iModel: IModelDb): boolean {\r\n return iModel.meetsMinimumSchemaVersion(\"BisCore\", minBisCoreVersion);\r\n }\r\n\r\n /** Examines all of the [FieldRun]($common)s within the specified [[ITextAnnotation]] and ensures that the appropriate\r\n * `ElementDrivesTextAnnotation` relationships exist between the fields' source elements and this target element.\r\n * It also deletes any stale relationships left over from fields that were deleted or whose source elements changed.\r\n * @deprecated in 5.9.0 - will not be removed until after 2027-05-04. Use ElementDrivesTextAnnotation.updateFieldDependencies(txn, ...) instead.\r\n */\r\n public static updateFieldDependencies(annotationElementId: Id64String, iModel: IModelDb): void;\r\n\r\n /** Examines all of the [FieldRun]($common)s within the specified [[ITextAnnotation]] and ensures that the appropriate\r\n * `ElementDrivesTextAnnotation` relationships exist between the fields' source elements and this target element.\r\n * It also deletes any stale relationships left over from fields that were deleted or whose source elements changed.\r\n */\r\n public static updateFieldDependencies(txn: EditTxn, annotationElementId: Id64String): void;\r\n public static updateFieldDependencies(arg1: EditTxn | Id64String, arg2: Id64String | IModelDb): void {\r\n if (arg1 instanceof EditTxn) {\r\n this.updateFieldDependenciesImpl(arg1, arg2 as Id64String);\r\n return;\r\n }\r\n\r\n this.updateFieldDependenciesImpl((arg2 as IModelDb)[_implicitTxn], arg1);\r\n }\r\n\r\n /** Recompute the display strings of all [FieldRun]($common)s in a [TextBlock]($common).\r\n * @returns the number of fields whose display strings were modified.\r\n * @throws Error if evaluation of any field fails.\r\n */\r\n public static evaluateFields(args: EvaluateFieldsArgs): number {\r\n return updateFields(args.block, createUpdateContext(undefined, args.iModel, false))\r\n }\r\n\r\n /** When copying an [[ITextAnnotation]] from one iModel into another, remaps the element Ids in any [FieldPropertyHost]($common) within the cloned element\r\n * so that they refer to elements in the `context`'s target iModel, and sets any Ids that cannot be remapped to [Id64.invalid]($bentley).\r\n * Implementations of `ITextAnnotation` should invoke this function from their implementations of [[Element.onCloned]].\r\n */\r\n public static remapFields(clone: ITextAnnotation, context: IModelElementCloneContext): void {\r\n if (!context.isBetweenIModels) {\r\n return;\r\n }\r\n\r\n const updatedBlocks = [];\r\n for (const block of clone.getTextBlocks()) {\r\n let anyUpdated = false;\r\n for (const { child } of traverseTextBlockComponent(block.textBlock)) {\r\n if (child.type === \"field\") {\r\n child.propertyHost.elementId = context.findTargetElementId(child.propertyHost.elementId);\r\n anyUpdated = true;\r\n }\r\n }\r\n\r\n if (anyUpdated) {\r\n updatedBlocks.push(block);\r\n }\r\n }\r\n\r\n if (updatedBlocks.length > 0) {\r\n clone.updateTextBlocks(updatedBlocks);\r\n }\r\n }\r\n}\r\n\r\n/** Relationship indicating that the [[AnnotationTextStyle]] is being used as the default style for the [[ITextAnnotation]].\r\n * @beta\r\n */\r\nexport class TextAnnotationUsesTextStyleByDefault extends RelatedElement {\r\n public static classFullName = \"BisCore:TextAnnotationUsesTextStyleByDefault\";\r\n public constructor(annotationTextStyleId: Id64String, relClassName: string = TextAnnotationUsesTextStyleByDefault.classFullName) {\r\n super({ id: annotationTextStyleId, relClassName });\r\n }\r\n}\r\n"]}
1
+ {"version":3,"file":"ElementDrivesTextAnnotation.js","sourceRoot":"","sources":["../../../src/annotations/ElementDrivesTextAnnotation.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n/** @packageDocumentation\r\n * @module Elements\r\n */\r\n\r\nimport { BeEvent, Id64, Id64String } from \"@itwin/core-bentley\";\r\nimport { QueryBinder, RelatedElement, TextBlock, traverseTextBlockComponent } from \"@itwin/core-common\";\r\nimport { UnitSystemKey } from \"@itwin/core-quantity\";\r\nimport { ECVersion, FormatSet } from \"@itwin/ecschema-metadata\";\r\nimport { Element } from \"../Element\";\r\nimport { IModelDb } from \"../IModelDb\";\r\nimport { IModelElementCloneContext } from \"../IModelElementCloneContext\";\r\nimport { createUpdateContext, updateAllFields, updateElementFields, updateFields } from \"../internal/annotations/fields\";\r\nimport { createFieldFormatting, setFieldFormatting } from \"../internal/annotations/fieldSpecs\";\r\nimport { _implicitTxn } from \"../internal/Symbols\";\r\nimport { ElementDrivesElement, OnDependencyArg } from \"../Relationship\";\r\nimport { EditTxn } from \"../EditTxn\";\r\n\r\n/** Configures how `\"quantity\"` and `\"coordinate\"` [FieldRun]($common)s in an iModel are\r\n * formatted. Passed to [[ElementDrivesTextAnnotation.registerFieldFormatting]].\r\n * @beta\r\n */\r\nexport interface FieldFormattingArgs {\r\n /** The iModel whose annotations this configuration formats. Its `schemaContext` supplies the\r\n * KindOfQuantity presentation formats that every FormatSet falls back to.\r\n */\r\n iModel: IModelDb;\r\n /** The FormatSet adopted for this iModel. It applies to every [FieldRun]($common) that does\r\n * not name a different one via [QuantityFieldFormatOptions.formatSet]($common), and takes\r\n * precedence over the schema's own presentation formats.\r\n */\r\n formatSet?: FormatSet;\r\n /** Additional FormatSets addressable per-field, each paired with the id that\r\n * [FieldRun]($common)s reference via [QuantityFieldFormatOptions.formatSet]($common).\r\n * The id must be unique; if two entries share an id the last one wins. A field naming an id\r\n * absent from this list falls back to [[formatSet]].\r\n */\r\n formatSets?: ReadonlyArray<{ id: string, formatSet: FormatSet }>;\r\n /** Unit system used to pick a KindOfQuantity's presentation format when the schema offers\r\n * several. Defaults to [[formatSet]]'s own `unitSystem`, or `\"metric\"` when no FormatSet is\r\n * adopted.\r\n */\r\n unitSystem?: UnitSystemKey;\r\n}\r\n\r\n/** Describes one of potentially many [TextBlock]($common)s hosted by an [[ITextAnnotation]].\r\n * For example, a [[TextAnnotation2d]] hosts only a single text block, but an element representing a table may\r\n * host one text block for each cell in the table, in which case it might use the combination of row and column\r\n * as the [[id]] for each text block.\r\n * @beta\r\n */\r\nexport interface TextBlockAndId {\r\n /** The text block. */\r\n readonly textBlock: TextBlock;\r\n /** An opaque identifier that allows the [[ITextAnnotation]] to discern which of its hosted text blocks is being referred to. */\r\n readonly id: unknown;\r\n}\r\n\r\n// ElementDrivesTextAnnotation was introduced in this version of BisCore - iModels with earlier versions cannot support field dependencies.\r\nconst minBisCoreVersion = new ECVersion(1, 0, 22);\r\n\r\n/** Interface implemented by [[GeometricElement]] subclasses whose schemas declare them to implement the mix-in `BisCore:ITextAnnotation`.\r\n * Such elements may host any number of [TextBlock]($common)s. `ITextAnnotation` provides a uniform way to interact with text regardless of\r\n * the type of element to which it belongs.\r\n * @beta\r\n */\r\nexport interface ITextAnnotation {\r\n /** The default [[AnnotationTextStyle]] used by the text annotation. */\r\n defaultTextStyle?: TextAnnotationUsesTextStyleByDefault;\r\n /** Obtain a collection of all of the [TextBlock]($common)s hosted by this element. */\r\n getTextBlocks(): Iterable<TextBlockAndId>;\r\n /** Update the element in-memory to replace the contents of the specified [TextBlock]($common)s. */\r\n updateTextBlocks(textBlocks: TextBlockAndId[]): void;\r\n}\r\n\r\n/** Returns `true` if the specified `element` implements [[ITextAnnotation]].\r\n * @beta\r\n */\r\nexport function isITextAnnotation(element: Element): element is ITextAnnotation & Element {\r\n return [\"getTextBlocks\", \"updateTextBlocks\"].every((x) => x in element && typeof (element as any)[x] === \"function\");\r\n}\r\n\r\n/** Arguments supplied to [[ElementDrivesTextAnnotation.evaluateFields]].\r\n * @beta\r\n */\r\nexport interface EvaluateFieldsArgs {\r\n /** The text block whose fields are to be evaluated. */\r\n block: TextBlock;\r\n /** The iModel containing the elements supplying the display strings for the fields in [[block]]. */\r\n iModel: IModelDb;\r\n}\r\n\r\n/** A relationship in which the source element hosts one or more properties that are displayed by a target [[ITextAnnotation]] element.\r\n * This relationship is used to automatically update the [FieldRun]($common)s contained in the target element when the source element is modified.\r\n * An [[ITextAnnotation]] element should invoke [[updateFieldDependencies]] from its [[Element.onInserted]] and [[Element.onUpdated]] functions to\r\n * establish or update the relationships required for the [FieldRun]($common)s it contains.\r\n * @note This relationship was introduced in version 01.00.22 of the BisCore schema. [FieldRun]($common)s created in iModels that have not been upgraded to\r\n * that version or newer will not automatically update. Use [[isSupportedForIModel]] to check.\r\n * @beta\r\n */\r\nexport class ElementDrivesTextAnnotation extends ElementDrivesElement {\r\n public static override get className(): string { return \"ElementDrivesTextAnnotation\"; }\r\n\r\n private static updateFieldDependenciesImpl(txn: EditTxn, annotationElementId: Id64String): void {\r\n const iModel = txn.iModel;\r\n const annotationElement = iModel.elements.tryGetElement<Element>(annotationElementId);\r\n if (!annotationElement || !isITextAnnotation(annotationElement)) {\r\n return;\r\n }\r\n\r\n // The native layer will allow us to insert relationships to invalid or non-existent source elements...errors will arise later. Prevent it.\r\n function isValidSourceId(id: Id64String): boolean {\r\n if (!Id64.isValidId64(id)) {\r\n return false;\r\n }\r\n\r\n return iModel.withQueryReader(\"SELECT CodeValue FROM BisCore.Element WHERE ECInstanceId=?\", (reader): boolean => {\r\n return reader.step();\r\n }, new QueryBinder().bindId(1, id));\r\n }\r\n\r\n const sourceToRelationship = new Map<Id64String, Id64String | null>();\r\n const blocks = annotationElement.getTextBlocks();\r\n\r\n let haveFields = false;\r\n for (const block of blocks) {\r\n for (const { child } of traverseTextBlockComponent(block.textBlock)) {\r\n if (child.type === \"field\") {\r\n haveFields = true;\r\n if (isValidSourceId(child.propertyHost.elementId)) {\r\n sourceToRelationship.set(child.propertyHost.elementId, null);\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (haveFields) {\r\n iModel.requireMinimumSchemaVersion(\"BisCore\", minBisCoreVersion, \"Text fields\");\r\n updateAllFields(annotationElementId, txn);\r\n }\r\n\r\n const staleRelationships = new Set<Id64String>();\r\n if (this.isSupportedForIModel(iModel)) {\r\n annotationElement.iModel.withQueryReader(\r\n \"SELECT ECInstanceId, SourceECInstanceId FROM BisCore.ElementDrivesTextAnnotation WHERE TargetECInstanceId=:targetId\",\r\n (reader) => {\r\n for (const row of reader) {\r\n const relationshipId: Id64String = row[0];\r\n const sourceId: Id64String = row[1];\r\n if (sourceToRelationship.has(sourceId)) {\r\n sourceToRelationship.set(sourceId, relationshipId);\r\n } else {\r\n staleRelationships.add(relationshipId);\r\n }\r\n }\r\n },\r\n new QueryBinder().bindId(\"targetId\", annotationElement.id),\r\n );\r\n }\r\n\r\n for (const [sourceId, relationshipId] of sourceToRelationship) {\r\n if (relationshipId === null) {\r\n txn.insertRelationship(ElementDrivesTextAnnotation.create(annotationElement.iModel, sourceId, annotationElement.id).toJSON());\r\n }\r\n }\r\n\r\n if (staleRelationships.size > 0) {\r\n const staleRelationshipProps = Array.from(staleRelationships).map((relationshipId) =>\r\n annotationElement.iModel.relationships.getInstanceProps(\"BisCore.ElementDrivesTextAnnotation\", relationshipId)\r\n );\r\n txn.deleteRelationships(staleRelationshipProps);\r\n }\r\n }\r\n\r\n /** @internal */\r\n public static override onRootChangedArg(arg: OnDependencyArg): void {\r\n updateElementFields(arg.props, arg.indirectEditTxn, false);\r\n }\r\n\r\n /** @internal */\r\n public static override onDeletedDependencyArg(arg: OnDependencyArg): void {\r\n updateElementFields(arg.props, arg.indirectEditTxn, true);\r\n }\r\n\r\n /** Returns true if `iModel` contains a version of the BisCore schema new enough to support this relationship.\r\n * If not, the schema should be updated before inserting any [FieldRun]($common)s, or those runs will not\r\n * update when the source element changes.\r\n */\r\n public static isSupportedForIModel(iModel: IModelDb): boolean {\r\n return iModel.meetsMinimumSchemaVersion(\"BisCore\", minBisCoreVersion);\r\n }\r\n\r\n /** Ensures the `ElementDrivesTextAnnotation` relationships for the [FieldRun]($common)s in the specified annotation are up to date.\r\n * @deprecated in 5.9.0 - will not be removed until after 2027-05-04. Use ElementDrivesTextAnnotation.updateFieldDependencies(txn, ...) instead.\r\n */\r\n public static updateFieldDependencies(annotationElementId: Id64String, iModel: IModelDb): void;\r\n\r\n /** Examines all of the [FieldRun]($common)s within the specified [[ITextAnnotation]] and ensures that the appropriate\r\n * `ElementDrivesTextAnnotation` relationships exist between the fields' source elements and this target element.\r\n * It also deletes any stale relationships left over from fields that were deleted or whose source elements changed.\r\n */\r\n public static updateFieldDependencies(txn: EditTxn, annotationElementId: Id64String): void;\r\n public static updateFieldDependencies(arg1: EditTxn | Id64String, arg2: Id64String | IModelDb): void {\r\n if (arg1 instanceof EditTxn) {\r\n this.updateFieldDependenciesImpl(arg1, arg2 as Id64String);\r\n return;\r\n }\r\n\r\n this.updateFieldDependenciesImpl((arg2 as IModelDb)[_implicitTxn], arg1);\r\n }\r\n\r\n /** Recompute the display strings of all [FieldRun]($common)s in a [TextBlock]($common).\r\n *\r\n * `\"quantity\"` and `\"coordinate\"` fields are formatted through the FormatSets configured for\r\n * `args.iModel` by [[registerFieldFormatting]], or otherwise through the format each\r\n * KindOfQuantity's schema declares.\r\n *\r\n * A field whose format cannot be resolved falls back to `value.toString()` and is logged. A\r\n * field whose property cannot be resolved, or whose format throws, is logged and rendered as\r\n * [FieldRun.invalidContentIndicator]($common); one bad field does not abandon the rest of the\r\n * block.\r\n * @returns the number of fields whose display strings were modified.\r\n */\r\n public static evaluateFields(args: EvaluateFieldsArgs): number {\r\n return updateFields(args.block, createUpdateContext(undefined, args.iModel, false));\r\n }\r\n\r\n /** Configures how `\"quantity\"` and `\"coordinate\"` [FieldRun]($common)s in `args.iModel` are\r\n * formatted by [[evaluateFields]] and by `TxnManager` field-update callbacks.\r\n *\r\n * Calling this is optional. An iModel with no registration formats every field using the\r\n * presentation format its schema declares for the KindOfQuantity, in the metric unit system.\r\n * Register to layer application [FormatSet]($ecschema-metadata)s over those schema defaults,\r\n * or to select a different unit system. Formats resolve in this order:\r\n *\r\n * 1. The FormatSet named by the field's [QuantityFieldFormatOptions.formatSet]($common).\r\n * 2. The FormatSet adopted for the iModel ([[FieldFormattingArgs.formatSet]]).\r\n * 3. The KindOfQuantity's presentation format for [[FieldFormattingArgs.unitSystem]].\r\n * 4. `value.toString()`, with a warning logged.\r\n *\r\n * Units resolve through the bundled BIS [BasicUnitsProvider]($core-quantity) only. A field\r\n * whose persistence unit, or whose format's units, are defined solely by the iModel's own\r\n * schemas falls to step 4.\r\n *\r\n * Pass the iModel-wide default as `formatSet` and any per-field alternatives as `formatSets`,\r\n * keyed by the id that [FieldRun]($common)s name via\r\n * [QuantityFieldFormatOptions.formatSet]($common):\r\n *\r\n * ```ts\r\n * ElementDrivesTextAnnotation.registerFieldFormatting({\r\n * iModel,\r\n * formatSet: metricFormatSet,\r\n * formatSets: [{ id: imperialFormatSetId, formatSet: imperial }],\r\n * });\r\n * ```\r\n *\r\n * Each call replaces any prior registration for the same iModel; calling it with only\r\n * `iModel` reverts to the schema defaults. Registering does not re-evaluate existing\r\n * annotations; listen to [[onFieldFormattingChanged]] to do so. The registration lives as long\r\n * as the `IModelDb` object and is released with it.\r\n * @see [Quantity formatting for text annotation fields]($docs/learning/backend/TextAnnotationFields.md)\r\n * @beta\r\n */\r\n public static registerFieldFormatting(args: FieldFormattingArgs): void {\r\n setFieldFormatting(args.iModel, createFieldFormatting(args));\r\n this.onFieldFormattingChanged.raiseEvent({ iModel: args.iModel });\r\n }\r\n\r\n /** Raised after every [[registerFieldFormatting]] call, once the new configuration is in\r\n * place, whether or not it differs from the previous one. Applications that cache formatted\r\n * output, or that want to re-evaluate existing annotations against a newly adopted FormatSet,\r\n * should listen here. A listener that throws does not undo the registration; its error is\r\n * reported through [UnexpectedErrors]($bentley) rather than to the caller of\r\n * `registerFieldFormatting`.\r\n * @beta\r\n */\r\n public static readonly onFieldFormattingChanged = new BeEvent<(args: { iModel: IModelDb }) => void>();\r\n\r\n /** When copying an [[ITextAnnotation]] from one iModel into another, remaps the element Ids in any [FieldPropertyHost]($common) within the cloned element\r\n * so that they refer to elements in the `context`'s target iModel, and sets any Ids that cannot be remapped to [Id64.invalid]($bentley).\r\n * Implementations of `ITextAnnotation` should invoke this function from their implementations of [[Element.onCloned]].\r\n */\r\n public static remapFields(clone: ITextAnnotation, context: IModelElementCloneContext): void {\r\n if (!context.isBetweenIModels) {\r\n return;\r\n }\r\n\r\n const updatedBlocks = [];\r\n for (const block of clone.getTextBlocks()) {\r\n let anyUpdated = false;\r\n for (const { child } of traverseTextBlockComponent(block.textBlock)) {\r\n if (child.type === \"field\") {\r\n child.propertyHost.elementId = context.findTargetElementId(child.propertyHost.elementId);\r\n anyUpdated = true;\r\n }\r\n }\r\n\r\n if (anyUpdated) {\r\n updatedBlocks.push(block);\r\n }\r\n }\r\n\r\n if (updatedBlocks.length > 0) {\r\n clone.updateTextBlocks(updatedBlocks);\r\n }\r\n }\r\n}\r\n\r\n/** Relationship indicating that the [[AnnotationTextStyle]] is being used as the default style for the [[ITextAnnotation]].\r\n * @beta\r\n */\r\nexport class TextAnnotationUsesTextStyleByDefault extends RelatedElement {\r\n public static classFullName = \"BisCore:TextAnnotationUsesTextStyleByDefault\";\r\n public constructor(annotationTextStyleId: Id64String, relClassName: string = TextAnnotationUsesTextStyleByDefault.classFullName) {\r\n super({ id: annotationTextStyleId, relClassName });\r\n }\r\n}\r\n"]}
@@ -0,0 +1,82 @@
1
+ import { FieldValue, QuantityFieldFormatOptions } from "@itwin/core-common";
2
+ import { FormatterSpec, FormattingSpecArgs, SyncFormatsProvider, SyncUnitsProvider } from "@itwin/core-quantity";
3
+ import { SchemaContext } from "@itwin/ecschema-metadata";
4
+ import type { FieldFormattingArgs } from "../../annotations/ElementDrivesTextAnnotation";
5
+ import type { IModelDb } from "../../IModelDb";
6
+ /** A [FieldValue]($common) as the backend resolves it: the primitive value plus, for
7
+ * `"quantity"` and `"coordinate"` types, the KindOfQuantity and persistence unit of the EC
8
+ * property it was read from. These serve as the fallback when the field's own
9
+ * [QuantityFieldFormatOptions]($common) do not resolve.
10
+ * @internal
11
+ */
12
+ export interface ResolvedFieldValue extends FieldValue {
13
+ /** EC full name of the property's KindOfQuantity, e.g. `"AecUnits.LENGTH"`, if it has one. */
14
+ kindOfQuantityFullName?: string;
15
+ /** EC full name of the unit the stored magnitude is expressed in, e.g. `"Units.M"`. */
16
+ persistenceUnitFullName?: string;
17
+ }
18
+ /** The formats an iModel's fields resolve through, as configured by
19
+ * [ElementDrivesTextAnnotation.registerFieldFormatting]($backend) or defaulted from the iModel's
20
+ * schemas. Holds only providers; every [FormatterSpec]($core-quantity) is built on demand by
21
+ * [[lookupFieldSpec]] and discarded after the field is formatted. Building one costs on the order
22
+ * of a microsecond, so nothing is cached.
23
+ * @internal
24
+ */
25
+ export interface FieldFormatting {
26
+ readonly schemaContext: SchemaContext;
27
+ readonly unitsProvider: SyncUnitsProvider;
28
+ /** The adopted FormatSet layered over the iModel's schema formats, or the schema formats alone. */
29
+ readonly defaultFormats: SyncFormatsProvider;
30
+ /** Per-field FormatSets by id, each falling through to [[defaultFormats]]. */
31
+ readonly formatSets: ReadonlyMap<string, SyncFormatsProvider>;
32
+ }
33
+ /** Returns the formats `iModel`'s fields resolve through, creating the schema-only default on
34
+ * first use.
35
+ * @internal
36
+ */
37
+ export declare function getFieldFormatting(iModel: IModelDb): FieldFormatting;
38
+ /** Replaces the formats `iModel`'s fields resolve through.
39
+ * @internal
40
+ */
41
+ export declare function setFieldFormatting(iModel: IModelDb, formatting: FieldFormatting): void;
42
+ /** @internal */
43
+ export declare function createFieldFormatting(args: FieldFormattingArgs): FieldFormatting;
44
+ /** Builds the [FormatterSpec]($core-quantity) for `args`, or `undefined` when the
45
+ * format fails to resolve, the persistence unit is not a bundled BIS unit, the format names a
46
+ * unit that is not, or the two cannot be converted between -- leaving the caller on the same
47
+ * fallback as any other unresolved override.
48
+ * @internal
49
+ */
50
+ export declare function buildFieldFormatterSpec(args: FormattingSpecArgs, formatsProvider: SyncFormatsProvider, formatting: FieldFormatting): FormatterSpec | undefined;
51
+ /** Builds the (KindOfQuantity, persistence unit) pairs a quantity/coordinate value may format
52
+ * through, in priority order: the field's override pair first, then the property's own pair.
53
+ * Names are compared exactly, so callers pass override names already normalized to the schema's
54
+ * spelling.
55
+ *
56
+ * The two halves fall back differently. `kindOfQuantity` only chooses how the magnitude is
57
+ * displayed, so the override wins and the property's is the fallback. `persistenceUnit` states
58
+ * what the stored magnitude *means*, so the property's own unit is authoritative when it has one;
59
+ * the override only supplies a unit for a property that lacks one (a coordinate, a plain double,
60
+ * a JSON leaf). See [[lookupFieldSpec]] for reporting an override that was ignored this way.
61
+ * @internal
62
+ */
63
+ export declare function collectFieldQuantityPairs(args: {
64
+ overrideName?: string;
65
+ overridePersistence?: string;
66
+ propertyName?: string;
67
+ propertyPersistence?: string;
68
+ }): FormattingSpecArgs[];
69
+ /** Builds the first [FormatterSpec]($core-quantity) that resolves for `value` among the formats
70
+ * of the FormatSet named by `quantityOptions.formatSet` -- or the adopted FormatSet and schema
71
+ * formats when it names none, or one that was never registered -- along with the pairs that were
72
+ * tried, so the caller can report when none resolved. `ignoredPersistenceUnit` is set when the
73
+ * field's `persistenceUnit` named a unit other than the property's own and was therefore not
74
+ * used, so the caller can report the likely authoring mistake.
75
+ * @internal
76
+ */
77
+ export declare function lookupFieldSpec(quantityOptions: QuantityFieldFormatOptions | undefined, value: ResolvedFieldValue, formatting: FieldFormatting): {
78
+ spec?: FormatterSpec;
79
+ candidates: FormattingSpecArgs[];
80
+ ignoredPersistenceUnit?: string;
81
+ };
82
+ //# sourceMappingURL=fieldSpecs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldSpecs.d.ts","sourceRoot":"","sources":["../../../../src/internal/annotations/fieldSpecs.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACuB,aAAa,EAAE,kBAAkB,EAAiB,mBAAmB,EAAE,iBAAiB,EACrH,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAA4B,aAAa,EAAgD,MAAM,0BAA0B,CAAC;AACjI,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC;AACzF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,WAAW,kBAAmB,SAAQ,UAAU;IACpD,8FAA8F;IAC9F,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uFAAuF;IACvF,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,iBAAiB,CAAC;IAC1C,mGAAmG;IACnG,QAAQ,CAAC,cAAc,EAAE,mBAAmB,CAAC;IAC7C,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC/D;AASD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAQpE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,GAAG,IAAI,CAEtF;AAED,gBAAgB;AAChB,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,eAAe,CAgBhF;AAqBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,kBAAkB,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS,CAqC9J;AAoBD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,GAAG,kBAAkB,EAAE,CAevB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,eAAe,EAAE,0BAA0B,GAAG,SAAS,EACvD,KAAK,EAAE,kBAAkB,EACzB,UAAU,EAAE,eAAe,GAC1B;IAAE,IAAI,CAAC,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,kBAAkB,EAAE,CAAC;IAAC,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAAE,CAoB7F"}
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getFieldFormatting = getFieldFormatting;
8
+ exports.setFieldFormatting = setFieldFormatting;
9
+ exports.createFieldFormatting = createFieldFormatting;
10
+ exports.buildFieldFormatterSpec = buildFieldFormatterSpec;
11
+ exports.collectFieldQuantityPairs = collectFieldQuantityPairs;
12
+ exports.lookupFieldSpec = lookupFieldSpec;
13
+ const core_quantity_1 = require("@itwin/core-quantity");
14
+ const ecschema_metadata_1 = require("@itwin/ecschema-metadata");
15
+ /** Per-iModel [[FieldFormatting]], as configured by the last
16
+ * [ElementDrivesTextAnnotation.registerFieldFormatting]($backend) call, or the schema-only default
17
+ * created the first time one of the iModel's fields was evaluated. Weakly keyed so a closed iModel
18
+ * takes its entry with it.
19
+ */
20
+ const fieldFormattings = new WeakMap();
21
+ /** Returns the formats `iModel`'s fields resolve through, creating the schema-only default on
22
+ * first use.
23
+ * @internal
24
+ */
25
+ function getFieldFormatting(iModel) {
26
+ let formatting = fieldFormattings.get(iModel);
27
+ if (!formatting) {
28
+ formatting = createFieldFormatting({ iModel });
29
+ fieldFormattings.set(iModel, formatting);
30
+ }
31
+ return formatting;
32
+ }
33
+ /** Replaces the formats `iModel`'s fields resolve through.
34
+ * @internal
35
+ */
36
+ function setFieldFormatting(iModel, formatting) {
37
+ fieldFormattings.set(iModel, formatting);
38
+ }
39
+ /** @internal */
40
+ function createFieldFormatting(args) {
41
+ // An adopted FormatSet declares the unit system it was authored for; honor it unless the
42
+ // caller says otherwise.
43
+ const unitSystem = args.unitSystem ?? args.formatSet?.unitSystem ?? "metric";
44
+ const schemaContext = args.iModel.schemaContext;
45
+ const schemaFormats = new ecschema_metadata_1.SchemaFormatsProvider(schemaContext, unitSystem);
46
+ // Every per-field FormatSet falls through to this same chain, so a field naming a FormatSet
47
+ // that has no entry for its KindOfQuantity still sees the adopted presentation before the
48
+ // schema's, matching the documented resolution order.
49
+ const defaultFormats = args.formatSet ? new ecschema_metadata_1.FormatSetFormatsProvider({ formatSet: args.formatSet, fallbackProvider: schemaFormats }) : schemaFormats;
50
+ const formatSets = new Map();
51
+ for (const { id, formatSet } of args.formatSets ?? []) {
52
+ formatSets.set(id, new ecschema_metadata_1.FormatSetFormatsProvider({ formatSet, fallbackProvider: defaultFormats }));
53
+ }
54
+ return { schemaContext, unitsProvider: new core_quantity_1.BasicUnitsProvider(), defaultFormats, formatSets };
55
+ }
56
+ /** [SchemaFormatsProvider.getFormatSync]($ecschema-metadata) reads only schemas already in the
57
+ * context's cache; it never asks a locater to load one. The backend locater *can* load
58
+ * synchronously, so ask the context for the schema first. That pulls it -- and the `Formats` and
59
+ * `Units` schemas it references -- into the cache. A name whose schema the iModel lacks (a key
60
+ * defined only by a FormatSet, say) is left for the formats provider to resolve or reject.
61
+ */
62
+ function ensureSchemaLoaded(context, fullName) {
63
+ const [schemaName] = ecschema_metadata_1.SchemaItem.parseFullName(fullName);
64
+ if (!schemaName) {
65
+ return;
66
+ }
67
+ try {
68
+ context.getSchemaSync(new ecschema_metadata_1.SchemaKey(schemaName));
69
+ }
70
+ catch {
71
+ // Not a schema this iModel knows.
72
+ }
73
+ }
74
+ /** Builds the [FormatterSpec]($core-quantity) for `args`, or `undefined` when the
75
+ * format fails to resolve, the persistence unit is not a bundled BIS unit, the format names a
76
+ * unit that is not, or the two cannot be converted between -- leaving the caller on the same
77
+ * fallback as any other unresolved override.
78
+ * @internal
79
+ */
80
+ function buildFieldFormatterSpec(args, formatsProvider, formatting) {
81
+ const { unitsProvider, schemaContext } = formatting;
82
+ ensureSchemaLoaded(schemaContext, args.name);
83
+ const formatProps = formatsProvider.getFormatSync(args.name, args.system);
84
+ if (!formatProps) {
85
+ return undefined;
86
+ }
87
+ // BasicUnitsProvider reports an unknown unit as an invalid BadUnit rather than throwing.
88
+ const persistenceUnit = unitsProvider.findUnitByNameSync(args.persistenceUnitName);
89
+ if (!persistenceUnit.isValid) {
90
+ return undefined;
91
+ }
92
+ let formatterSpec;
93
+ try {
94
+ const format = core_quantity_1.Format.createFromJSONSync("fieldFormat", unitsProvider, formatProps);
95
+ formatterSpec = core_quantity_1.FormatterSpec.createSync("fieldFormat", format, unitsProvider, persistenceUnit);
96
+ }
97
+ catch (err) {
98
+ // A format naming a unit the bundled provider lacks can't be resolved.
99
+ if (err instanceof core_quantity_1.QuantityError) {
100
+ return undefined;
101
+ }
102
+ throw err;
103
+ }
104
+ // Reject a format whose units belong to a different phenomenon than the persisted value.
105
+ // SyncUnitsProvider.getConversionSync reports this by returning the identity conversion tagged
106
+ // `error: true`, and its contract requires callers to check that flag before applying the
107
+ // result. FormatterSpec.createSync only logs a warning and keeps the identity conversion, so
108
+ // without this check a length persisted in meters would render through an angle format as
109
+ // "2.5 deg" -- relabelled rather than converted.
110
+ if (formatterSpec.unitConversions.some((conversion) => conversion.conversion.error)) {
111
+ return undefined;
112
+ }
113
+ return formatterSpec;
114
+ }
115
+ /** Returns `name` as the iModel's schemas spell it (`Units.M` for `units:m`), so later lookups
116
+ * and comparisons can be exact: [BasicUnitsProvider.findUnitByNameSync]($core-quantity) only
117
+ * recognizes the canonical spelling. An empty name counts as unset. A name no schema defines,
118
+ * such as a KindOfQuantity only a FormatSet declares, is returned unchanged.
119
+ */
120
+ function normalizeToSchemaName(context, name) {
121
+ if (!name) {
122
+ return undefined;
123
+ }
124
+ try {
125
+ return context.getSchemaItemSync(name)?.fullName ?? name;
126
+ }
127
+ catch {
128
+ // Not a schema this iModel knows.
129
+ return name;
130
+ }
131
+ }
132
+ /** Builds the (KindOfQuantity, persistence unit) pairs a quantity/coordinate value may format
133
+ * through, in priority order: the field's override pair first, then the property's own pair.
134
+ * Names are compared exactly, so callers pass override names already normalized to the schema's
135
+ * spelling.
136
+ *
137
+ * The two halves fall back differently. `kindOfQuantity` only chooses how the magnitude is
138
+ * displayed, so the override wins and the property's is the fallback. `persistenceUnit` states
139
+ * what the stored magnitude *means*, so the property's own unit is authoritative when it has one;
140
+ * the override only supplies a unit for a property that lacks one (a coordinate, a plain double,
141
+ * a JSON leaf). See [[lookupFieldSpec]] for reporting an override that was ignored this way.
142
+ * @internal
143
+ */
144
+ function collectFieldQuantityPairs(args) {
145
+ const { overrideName, overridePersistence, propertyName, propertyPersistence } = args;
146
+ const name = overrideName ?? propertyName;
147
+ const persistenceUnitName = propertyPersistence ?? overridePersistence;
148
+ const pairs = [];
149
+ if (name && persistenceUnitName) {
150
+ pairs.push({ name, persistenceUnitName });
151
+ }
152
+ // Fall back to the property's own pair unless the pair above already is it.
153
+ if (propertyName && propertyPersistence && name !== propertyName) {
154
+ pairs.push({ name: propertyName, persistenceUnitName: propertyPersistence });
155
+ }
156
+ return pairs;
157
+ }
158
+ /** Builds the first [FormatterSpec]($core-quantity) that resolves for `value` among the formats
159
+ * of the FormatSet named by `quantityOptions.formatSet` -- or the adopted FormatSet and schema
160
+ * formats when it names none, or one that was never registered -- along with the pairs that were
161
+ * tried, so the caller can report when none resolved. `ignoredPersistenceUnit` is set when the
162
+ * field's `persistenceUnit` named a unit other than the property's own and was therefore not
163
+ * used, so the caller can report the likely authoring mistake.
164
+ * @internal
165
+ */
166
+ function lookupFieldSpec(quantityOptions, value, formatting) {
167
+ const overridePersistence = normalizeToSchemaName(formatting.schemaContext, quantityOptions?.persistenceUnit);
168
+ const propertyPersistence = value.persistenceUnitFullName;
169
+ const ignoredPersistenceUnit = overridePersistence && propertyPersistence && overridePersistence !== propertyPersistence
170
+ ? overridePersistence : undefined;
171
+ const candidates = collectFieldQuantityPairs({
172
+ overrideName: normalizeToSchemaName(formatting.schemaContext, quantityOptions?.kindOfQuantity),
173
+ overridePersistence,
174
+ propertyName: value.kindOfQuantityFullName,
175
+ propertyPersistence,
176
+ });
177
+ const formatSet = quantityOptions?.formatSet;
178
+ const formats = (formatSet ? formatting.formatSets.get(formatSet) : undefined) ?? formatting.defaultFormats;
179
+ for (const candidate of candidates) {
180
+ const spec = buildFieldFormatterSpec(candidate, formats, formatting);
181
+ if (spec) {
182
+ return { spec, candidates, ignoredPersistenceUnit };
183
+ }
184
+ }
185
+ return { candidates, ignoredPersistenceUnit };
186
+ }
187
+ //# sourceMappingURL=fieldSpecs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldSpecs.js","sourceRoot":"","sources":["../../../../src/internal/annotations/fieldSpecs.ts"],"names":[],"mappings":"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","sourcesContent":["/*---------------------------------------------------------------------------------------------\r\n* Copyright (c) Bentley Systems, Incorporated. All rights reserved.\r\n* See LICENSE.md in the project root for license terms and full copyright notice.\r\n*--------------------------------------------------------------------------------------------*/\r\n\r\nimport { FieldValue, QuantityFieldFormatOptions } from \"@itwin/core-common\";\r\nimport {\r\n BasicUnitsProvider, Format, FormatterSpec, FormattingSpecArgs, QuantityError, SyncFormatsProvider, SyncUnitsProvider,\r\n} from \"@itwin/core-quantity\";\r\nimport { FormatSetFormatsProvider, SchemaContext, SchemaFormatsProvider, SchemaItem, SchemaKey } from \"@itwin/ecschema-metadata\";\r\nimport type { FieldFormattingArgs } from \"../../annotations/ElementDrivesTextAnnotation\";\r\nimport type { IModelDb } from \"../../IModelDb\";\r\n\r\n/** A [FieldValue]($common) as the backend resolves it: the primitive value plus, for\r\n * `\"quantity\"` and `\"coordinate\"` types, the KindOfQuantity and persistence unit of the EC\r\n * property it was read from. These serve as the fallback when the field's own\r\n * [QuantityFieldFormatOptions]($common) do not resolve.\r\n * @internal\r\n */\r\nexport interface ResolvedFieldValue extends FieldValue {\r\n /** EC full name of the property's KindOfQuantity, e.g. `\"AecUnits.LENGTH\"`, if it has one. */\r\n kindOfQuantityFullName?: string;\r\n /** EC full name of the unit the stored magnitude is expressed in, e.g. `\"Units.M\"`. */\r\n persistenceUnitFullName?: string;\r\n}\r\n\r\n/** The formats an iModel's fields resolve through, as configured by\r\n * [ElementDrivesTextAnnotation.registerFieldFormatting]($backend) or defaulted from the iModel's\r\n * schemas. Holds only providers; every [FormatterSpec]($core-quantity) is built on demand by\r\n * [[lookupFieldSpec]] and discarded after the field is formatted. Building one costs on the order\r\n * of a microsecond, so nothing is cached.\r\n * @internal\r\n */\r\nexport interface FieldFormatting {\r\n readonly schemaContext: SchemaContext;\r\n readonly unitsProvider: SyncUnitsProvider;\r\n /** The adopted FormatSet layered over the iModel's schema formats, or the schema formats alone. */\r\n readonly defaultFormats: SyncFormatsProvider;\r\n /** Per-field FormatSets by id, each falling through to [[defaultFormats]]. */\r\n readonly formatSets: ReadonlyMap<string, SyncFormatsProvider>;\r\n}\r\n\r\n/** Per-iModel [[FieldFormatting]], as configured by the last\r\n * [ElementDrivesTextAnnotation.registerFieldFormatting]($backend) call, or the schema-only default\r\n * created the first time one of the iModel's fields was evaluated. Weakly keyed so a closed iModel\r\n * takes its entry with it.\r\n */\r\nconst fieldFormattings = new WeakMap<IModelDb, FieldFormatting>();\r\n\r\n/** Returns the formats `iModel`'s fields resolve through, creating the schema-only default on\r\n * first use.\r\n * @internal\r\n */\r\nexport function getFieldFormatting(iModel: IModelDb): FieldFormatting {\r\n let formatting = fieldFormattings.get(iModel);\r\n if (!formatting) {\r\n formatting = createFieldFormatting({ iModel });\r\n fieldFormattings.set(iModel, formatting);\r\n }\r\n\r\n return formatting;\r\n}\r\n\r\n/** Replaces the formats `iModel`'s fields resolve through.\r\n * @internal\r\n */\r\nexport function setFieldFormatting(iModel: IModelDb, formatting: FieldFormatting): void {\r\n fieldFormattings.set(iModel, formatting);\r\n}\r\n\r\n/** @internal */\r\nexport function createFieldFormatting(args: FieldFormattingArgs): FieldFormatting {\r\n // An adopted FormatSet declares the unit system it was authored for; honor it unless the\r\n // caller says otherwise.\r\n const unitSystem = args.unitSystem ?? args.formatSet?.unitSystem ?? \"metric\";\r\n const schemaContext = args.iModel.schemaContext;\r\n const schemaFormats = new SchemaFormatsProvider(schemaContext, unitSystem);\r\n // Every per-field FormatSet falls through to this same chain, so a field naming a FormatSet\r\n // that has no entry for its KindOfQuantity still sees the adopted presentation before the\r\n // schema's, matching the documented resolution order.\r\n const defaultFormats = args.formatSet ? new FormatSetFormatsProvider({ formatSet: args.formatSet, fallbackProvider: schemaFormats }) : schemaFormats;\r\n const formatSets = new Map<string, SyncFormatsProvider>();\r\n for (const { id, formatSet } of args.formatSets ?? []) {\r\n formatSets.set(id, new FormatSetFormatsProvider({ formatSet, fallbackProvider: defaultFormats }));\r\n }\r\n\r\n return { schemaContext, unitsProvider: new BasicUnitsProvider(), defaultFormats, formatSets };\r\n}\r\n\r\n/** [SchemaFormatsProvider.getFormatSync]($ecschema-metadata) reads only schemas already in the\r\n * context's cache; it never asks a locater to load one. The backend locater *can* load\r\n * synchronously, so ask the context for the schema first. That pulls it -- and the `Formats` and\r\n * `Units` schemas it references -- into the cache. A name whose schema the iModel lacks (a key\r\n * defined only by a FormatSet, say) is left for the formats provider to resolve or reject.\r\n */\r\nfunction ensureSchemaLoaded(context: SchemaContext, fullName: string): void {\r\n const [schemaName] = SchemaItem.parseFullName(fullName);\r\n if (!schemaName) {\r\n return;\r\n }\r\n\r\n try {\r\n context.getSchemaSync(new SchemaKey(schemaName));\r\n } catch {\r\n // Not a schema this iModel knows.\r\n }\r\n}\r\n\r\n/** Builds the [FormatterSpec]($core-quantity) for `args`, or `undefined` when the\r\n * format fails to resolve, the persistence unit is not a bundled BIS unit, the format names a\r\n * unit that is not, or the two cannot be converted between -- leaving the caller on the same\r\n * fallback as any other unresolved override.\r\n * @internal\r\n */\r\nexport function buildFieldFormatterSpec(args: FormattingSpecArgs, formatsProvider: SyncFormatsProvider, formatting: FieldFormatting): FormatterSpec | undefined {\r\n const { unitsProvider, schemaContext } = formatting;\r\n ensureSchemaLoaded(schemaContext, args.name);\r\n const formatProps = formatsProvider.getFormatSync(args.name, args.system);\r\n if (!formatProps) {\r\n return undefined;\r\n }\r\n\r\n // BasicUnitsProvider reports an unknown unit as an invalid BadUnit rather than throwing.\r\n const persistenceUnit = unitsProvider.findUnitByNameSync(args.persistenceUnitName);\r\n if (!persistenceUnit.isValid) {\r\n return undefined;\r\n }\r\n\r\n let formatterSpec: FormatterSpec;\r\n try {\r\n const format = Format.createFromJSONSync(\"fieldFormat\", unitsProvider, formatProps);\r\n formatterSpec = FormatterSpec.createSync(\"fieldFormat\", format, unitsProvider, persistenceUnit);\r\n } catch (err) {\r\n // A format naming a unit the bundled provider lacks can't be resolved.\r\n if (err instanceof QuantityError) {\r\n return undefined;\r\n }\r\n throw err;\r\n }\r\n\r\n // Reject a format whose units belong to a different phenomenon than the persisted value.\r\n // SyncUnitsProvider.getConversionSync reports this by returning the identity conversion tagged\r\n // `error: true`, and its contract requires callers to check that flag before applying the\r\n // result. FormatterSpec.createSync only logs a warning and keeps the identity conversion, so\r\n // without this check a length persisted in meters would render through an angle format as\r\n // \"2.5 deg\" -- relabelled rather than converted.\r\n if (formatterSpec.unitConversions.some((conversion) => conversion.conversion.error)) {\r\n return undefined;\r\n }\r\n\r\n return formatterSpec;\r\n}\r\n\r\n/** Returns `name` as the iModel's schemas spell it (`Units.M` for `units:m`), so later lookups\r\n * and comparisons can be exact: [BasicUnitsProvider.findUnitByNameSync]($core-quantity) only\r\n * recognizes the canonical spelling. An empty name counts as unset. A name no schema defines,\r\n * such as a KindOfQuantity only a FormatSet declares, is returned unchanged.\r\n */\r\nfunction normalizeToSchemaName(context: SchemaContext, name: string | undefined): string | undefined {\r\n if (!name) {\r\n return undefined;\r\n }\r\n\r\n try {\r\n return context.getSchemaItemSync(name)?.fullName ?? name;\r\n } catch {\r\n // Not a schema this iModel knows.\r\n return name;\r\n }\r\n}\r\n\r\n/** Builds the (KindOfQuantity, persistence unit) pairs a quantity/coordinate value may format\r\n * through, in priority order: the field's override pair first, then the property's own pair.\r\n * Names are compared exactly, so callers pass override names already normalized to the schema's\r\n * spelling.\r\n *\r\n * The two halves fall back differently. `kindOfQuantity` only chooses how the magnitude is\r\n * displayed, so the override wins and the property's is the fallback. `persistenceUnit` states\r\n * what the stored magnitude *means*, so the property's own unit is authoritative when it has one;\r\n * the override only supplies a unit for a property that lacks one (a coordinate, a plain double,\r\n * a JSON leaf). See [[lookupFieldSpec]] for reporting an override that was ignored this way.\r\n * @internal\r\n */\r\nexport function collectFieldQuantityPairs(args: {\r\n overrideName?: string;\r\n overridePersistence?: string;\r\n propertyName?: string;\r\n propertyPersistence?: string;\r\n}): FormattingSpecArgs[] {\r\n const { overrideName, overridePersistence, propertyName, propertyPersistence } = args;\r\n const name = overrideName ?? propertyName;\r\n const persistenceUnitName = propertyPersistence ?? overridePersistence;\r\n const pairs: FormattingSpecArgs[] = [];\r\n if (name && persistenceUnitName) {\r\n pairs.push({ name, persistenceUnitName });\r\n }\r\n\r\n // Fall back to the property's own pair unless the pair above already is it.\r\n if (propertyName && propertyPersistence && name !== propertyName) {\r\n pairs.push({ name: propertyName, persistenceUnitName: propertyPersistence });\r\n }\r\n\r\n return pairs;\r\n}\r\n\r\n/** Builds the first [FormatterSpec]($core-quantity) that resolves for `value` among the formats\r\n * of the FormatSet named by `quantityOptions.formatSet` -- or the adopted FormatSet and schema\r\n * formats when it names none, or one that was never registered -- along with the pairs that were\r\n * tried, so the caller can report when none resolved. `ignoredPersistenceUnit` is set when the\r\n * field's `persistenceUnit` named a unit other than the property's own and was therefore not\r\n * used, so the caller can report the likely authoring mistake.\r\n * @internal\r\n */\r\nexport function lookupFieldSpec(\r\n quantityOptions: QuantityFieldFormatOptions | undefined,\r\n value: ResolvedFieldValue,\r\n formatting: FieldFormatting,\r\n): { spec?: FormatterSpec, candidates: FormattingSpecArgs[], ignoredPersistenceUnit?: string } {\r\n const overridePersistence = normalizeToSchemaName(formatting.schemaContext, quantityOptions?.persistenceUnit);\r\n const propertyPersistence = value.persistenceUnitFullName;\r\n const ignoredPersistenceUnit = overridePersistence && propertyPersistence && overridePersistence !== propertyPersistence\r\n ? overridePersistence : undefined;\r\n const candidates = collectFieldQuantityPairs({\r\n overrideName: normalizeToSchemaName(formatting.schemaContext, quantityOptions?.kindOfQuantity),\r\n overridePersistence,\r\n propertyName: value.kindOfQuantityFullName,\r\n propertyPersistence,\r\n });\r\n const formatSet = quantityOptions?.formatSet;\r\n const formats = (formatSet ? formatting.formatSets.get(formatSet) : undefined) ?? formatting.defaultFormats;\r\n for (const candidate of candidates) {\r\n const spec = buildFieldFormatterSpec(candidate, formats, formatting);\r\n if (spec) {\r\n return { spec, candidates, ignoredPersistenceUnit };\r\n }\r\n }\r\n return { candidates, ignoredPersistenceUnit };\r\n}\r\n"]}