@smartbit4all/ng-client 7.3.37 → 7.3.38
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/HTML-WIDGET.md +3 -2
- package/fesm2022/smartbit4all-ng-client.mjs +178 -67
- package/fesm2022/smartbit4all-ng-client.mjs.map +1 -1
- package/package.json +1 -1
- package/smartbit4all-ng-client-7.3.38.tgz +0 -0
- package/types/smartbit4all-ng-client.d.ts +4 -0
- package/smartbit4all-ng-client-7.3.37.tgz +0 -0
package/HTML-WIDGET.md
CHANGED
|
@@ -14,7 +14,7 @@ html-t **két fázisban** oldjuk fel, két külön jelöléssel:
|
|
|
14
14
|
| Fázis | Jelölés | Ki oldja fel | Mire jó |
|
|
15
15
|
|---|---|---|---|
|
|
16
16
|
| szerver-helyőrző | `${…}` | a backend, a page-ben, mielőtt a html elhagyja | feltétel, iteráció, szkript, lokalizáció — a platform meglévő template-dialektusa |
|
|
17
|
-
| kliens-helyőrző | `{{…}}` | a kliens, a widget **feloldási kontextusán** | ugyanaz a html egyszer utazik és soronként / modell-változásonként újra feloldódik |
|
|
17
|
+
| kliens-helyőrző | `{{…}}` | a kliens, a widget **feloldási kontextusán** | ugyanaz a html egyszer utazik és soronként / modell-változásonként, a form egy kontrolljának minden szerkesztésére újra feloldódik |
|
|
18
18
|
|
|
19
19
|
A kliens a szerver-fázisból semmit nem lát: mire a html megérkezik, a `${…}` már nincs benne.
|
|
20
20
|
|
|
@@ -40,7 +40,8 @@ A `DIV` widget változatlanul megmarad; a HTML widget annak a használatnak a re
|
|
|
40
40
|
| `css` | a widget stíluslapja, a html mellett utazik — lásd *Stíluslap* |
|
|
41
41
|
| `icons` | az **ikon-szótár**: `kulcs → ImageResource`, amit az ikon-slotok `data-sb4-icon-key`-jel címeznek — lásd *Ikon-slot* |
|
|
42
42
|
|
|
43
|
-
A **feloldási kontextus** nézeten a `ComponentModel` (`data` + `valueSets`),
|
|
43
|
+
A **feloldási kontextus** nézeten a `ComponentModel` (`data` + `valueSets`), a `data`-ban a form
|
|
44
|
+
kontrolljainak még be nem küldött szerkesztéseivel, grid-kártyán a sor
|
|
44
45
|
(`GridRow.data`) a gridet mutató oldal `valueSets`-ével. Ha a `root` listát, egyetlen értéket vagy
|
|
45
46
|
semmit nevez, minden helyőrző üres marad, és a kliens egyszer `console.warn`-t ír.
|
|
46
47
|
|
|
@@ -4320,6 +4320,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
4320
4320
|
args: [{ selector: '[smartEmbeddedSlot]' }]
|
|
4321
4321
|
}], propDecorators: { containerId: [{ type: i0.Input, args: [{ isSignal: true, alias: "smartEmbeddedSlot", required: true }] }], containerUuid: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerUuid", required: false }] }] } });
|
|
4322
4322
|
|
|
4323
|
+
/** Uri text as RFC 3986 gives it, the URI_PATTERN of UriUtils on the server. */
|
|
4324
|
+
const URI_PATTERN = new RegExp('^(?:(?:[a-zA-Z][a-zA-Z0-9+.-]*):)' + // scheme
|
|
4325
|
+
"(?://(?:[\\w\\-._~%!$&'()*+,;=:]+@)?" + // userinfo
|
|
4326
|
+
'(?:\\[[0-9A-Fa-f:.]+\\]|[\\w\\-._~%]+)' + // host (IP-literal / IPv4 / name)
|
|
4327
|
+
'(?::\\d{2,5})?)?' + // port
|
|
4328
|
+
"(?:/[\\w\\-._~%!$&'()*+,;=:@]*)*" + // path
|
|
4329
|
+
"(?:\\?[\\w\\-._~%!$&'()*+,;=:@/?]*)?" + // query
|
|
4330
|
+
"(?:#[\\w\\-._~%!$&'()*+,;=:@/?]*)?$" // fragment
|
|
4331
|
+
);
|
|
4332
|
+
/** The version suffix of the storage at the end of a path. */
|
|
4333
|
+
const VERSION_SUFFIX = /\.v(?:0|[1-9]\d*)$/;
|
|
4323
4334
|
/**
|
|
4324
4335
|
* The key and display property of a valueSet's values: the preferred name if the values carry it,
|
|
4325
4336
|
* then the GenericValue name (`uri` / `name`), then the Value name (`objectUri` / `displayValue`).
|
|
@@ -4331,26 +4342,66 @@ function valueSetKeyProperty(values, preferred) {
|
|
|
4331
4342
|
function valueSetDisplayProperty(values, preferred) {
|
|
4332
4343
|
return firstExisting(values, [preferred ?? 'displayValue', 'name', 'displayValue']);
|
|
4333
4344
|
}
|
|
4345
|
+
/**
|
|
4346
|
+
* The property of a value: a key the value has as it is, else a dotted path navigated part by
|
|
4347
|
+
* part (`data.name`); undefined when a part is missing.
|
|
4348
|
+
*/
|
|
4349
|
+
function readValueSetProperty(value, property) {
|
|
4350
|
+
if (value === null || typeof value !== 'object' || property in value) {
|
|
4351
|
+
return value?.[property];
|
|
4352
|
+
}
|
|
4353
|
+
return property.split('.').reduce((part, name) => part?.[name], value);
|
|
4354
|
+
}
|
|
4334
4355
|
/** The label of a code in a valueSet, or undefined when the valueSet does not know the code. */
|
|
4335
4356
|
function valueSetLabelOf(valueSetData, code) {
|
|
4336
4357
|
const values = valueSetData.values ?? [];
|
|
4337
4358
|
const keyProperty = valueSetKeyProperty(values, valueSetData.keyProperty);
|
|
4338
|
-
const hit = values.find((value) =>
|
|
4339
|
-
return hit
|
|
4359
|
+
const hit = values.find((value) => isSameKey(readValueSetProperty(value, keyProperty), code, valueSetData.versionedKeys));
|
|
4360
|
+
return hit && readValueSetProperty(hit, valueSetDisplayProperty(values));
|
|
4340
4361
|
}
|
|
4341
4362
|
function firstExisting(values, candidates) {
|
|
4342
4363
|
if (!values?.length) {
|
|
4343
4364
|
return candidates[0];
|
|
4344
4365
|
}
|
|
4345
|
-
|
|
4366
|
+
// A candidate exists when the first value reads it, also as a dotted path, or has it as a key.
|
|
4367
|
+
return (candidates.find((c) => readValueSetProperty(values[0], c) !== undefined || c in values[0]) ??
|
|
4368
|
+
candidates[candidates.length - 1]);
|
|
4346
4369
|
}
|
|
4347
4370
|
/** Whether a field holds no value: `''`, null, undefined or an empty list. */
|
|
4348
4371
|
function isEmptyValue(value) {
|
|
4349
4372
|
return value === '' || value == null || (Array.isArray(value) && value.length === 0);
|
|
4350
4373
|
}
|
|
4351
|
-
/**
|
|
4352
|
-
|
|
4353
|
-
|
|
4374
|
+
/**
|
|
4375
|
+
* The key as the keys of a set compare: as text, a uri text (RFC 3986) without the version suffix
|
|
4376
|
+
* at the end of its path unless the set has Versioned Keys. Any other text stays as it is, a code
|
|
4377
|
+
* ending in a version too.
|
|
4378
|
+
*/
|
|
4379
|
+
function createReferenceKey(key, versionedKeys) {
|
|
4380
|
+
const text = String(key);
|
|
4381
|
+
return versionedKeys || !URI_PATTERN.test(text) ? text : removeVersion(text);
|
|
4382
|
+
}
|
|
4383
|
+
/**
|
|
4384
|
+
* The uri text without the version suffix at the end of its path, as removeVersion of UriUtils on
|
|
4385
|
+
* the server; the scheme, the authority, the query and the fragment stay as they are.
|
|
4386
|
+
*/
|
|
4387
|
+
function removeVersion(uri) {
|
|
4388
|
+
const schemeEnd = uri.indexOf(':');
|
|
4389
|
+
const queryOrFragment = uri.slice(schemeEnd + 1).search(/[?#]/);
|
|
4390
|
+
const pathEnd = queryOrFragment < 0 ? uri.length : schemeEnd + 1 + queryOrFragment;
|
|
4391
|
+
let pathStart = schemeEnd + 1;
|
|
4392
|
+
if (uri.startsWith('//', pathStart)) {
|
|
4393
|
+
const slash = uri.indexOf('/', pathStart + 2);
|
|
4394
|
+
pathStart = slash < 0 || slash > pathEnd ? pathEnd : slash;
|
|
4395
|
+
}
|
|
4396
|
+
const version = VERSION_SUFFIX.exec(uri.slice(pathStart, pathEnd));
|
|
4397
|
+
return version ? uri.slice(0, pathStart + version.index) + uri.slice(pathEnd) : uri;
|
|
4398
|
+
}
|
|
4399
|
+
/**
|
|
4400
|
+
* Whether two keys of a set are equal as {@link createReferenceKey} gives them; as text, as a
|
|
4401
|
+
* control holds a boolean key as text.
|
|
4402
|
+
*/
|
|
4403
|
+
function isSameKey(key1, key2, versionedKeys) {
|
|
4404
|
+
return createReferenceKey(key1, versionedKeys) === createReferenceKey(key2, versionedKeys);
|
|
4354
4405
|
}
|
|
4355
4406
|
|
|
4356
4407
|
/**
|
|
@@ -4456,7 +4507,7 @@ class SmartValueSetMemoryStore extends SmartValueSetStore {
|
|
|
4456
4507
|
}
|
|
4457
4508
|
const held = this.viewEntries.get(viewUuid)?.get(key);
|
|
4458
4509
|
const extras = {
|
|
4459
|
-
extras: addReceivedExtras(held?.extras, valueSet.extraValues),
|
|
4510
|
+
extras: addReceivedExtras(held?.extras, valueSet.extraValues, this.findVersionedKeys(viewUuid, key, valueSet, held)),
|
|
4460
4511
|
excludedKeys: valueSet.excludedKeys,
|
|
4461
4512
|
};
|
|
4462
4513
|
// A version alone keeps the values held on screen until the fetch brings the new ones.
|
|
@@ -4879,6 +4930,18 @@ class SmartValueSetMemoryStore extends SmartValueSetStore {
|
|
|
4879
4930
|
});
|
|
4880
4931
|
changedNames.forEach((name) => this.changeSource.next(name));
|
|
4881
4932
|
}
|
|
4933
|
+
/**
|
|
4934
|
+
* The Versioned Keys of the view's set of the key: of the values it receives or holds, else of
|
|
4935
|
+
* the explicit set its selection reads; undefined while the client has none of them.
|
|
4936
|
+
*/
|
|
4937
|
+
findVersionedKeys(viewUuid, key, valueSet, held) {
|
|
4938
|
+
const selection = this.viewSelections.get(viewUuid)?.get(key);
|
|
4939
|
+
const data = valueSet.valueSetData ??
|
|
4940
|
+
held?.data ??
|
|
4941
|
+
(selection &&
|
|
4942
|
+
this.findExplicitEntry(viewUuid, selection.valueSetNamespace, key)?.data);
|
|
4943
|
+
return data ? !!data.versionedKeys : undefined;
|
|
4944
|
+
}
|
|
4882
4945
|
mergeViewExtras(data, viewEntry, selection) {
|
|
4883
4946
|
return viewEntry?.extras?.length || viewEntry?.excludedKeys?.length
|
|
4884
4947
|
? mergeExtraValues(data, viewEntry, selection)
|
|
@@ -4911,31 +4974,46 @@ class SmartValueSetMemoryStore extends SmartValueSetStore {
|
|
|
4911
4974
|
this.changeSource.next(key);
|
|
4912
4975
|
}
|
|
4913
4976
|
}
|
|
4914
|
-
/**
|
|
4915
|
-
|
|
4916
|
-
|
|
4977
|
+
/**
|
|
4978
|
+
* The held extras followed by the received ones of a code not held yet, compared as
|
|
4979
|
+
* {@link createReferenceKey} gives it. While the Versioned Keys of the set are unknown the codes
|
|
4980
|
+
* compare exactly, the merge with the set drops the second version of an object then.
|
|
4981
|
+
*/
|
|
4982
|
+
function addReceivedExtras(held, received, versionedKeys) {
|
|
4983
|
+
const keepsVersion = versionedKeys !== false;
|
|
4984
|
+
const heldCodes = new Set((held ?? []).map((extra) => createReferenceKey(extra.value.code, keepsVersion)));
|
|
4917
4985
|
const added = (received ?? [])
|
|
4918
|
-
.filter((value) => !heldCodes.has(value.code))
|
|
4986
|
+
.filter((value) => !heldCodes.has(createReferenceKey(value.code, keepsVersion)))
|
|
4919
4987
|
.map((value) => ({ value }));
|
|
4920
4988
|
return [...(held ?? []), ...added];
|
|
4921
4989
|
}
|
|
4922
4990
|
/**
|
|
4923
|
-
* The set without the values whose
|
|
4924
|
-
* view's extras keyed by their code under the selection's key property, none the set
|
|
4991
|
+
* The set without the values whose key at the set's key property is an excluded key, then the
|
|
4992
|
+
* view's extras keyed by their code under the selection's key property, none the set or an extra
|
|
4993
|
+
* before has; the keys compare as {@link createReferenceKey} gives them.
|
|
4925
4994
|
*/
|
|
4926
4995
|
function mergeExtraValues(data, extras, selection) {
|
|
4927
4996
|
const values = data.values ?? [];
|
|
4928
4997
|
const keyProperty = valueSetKeyProperty(values, selection.keyProperty ?? data.keyProperty);
|
|
4929
4998
|
const displayProperty = valueSetDisplayProperty(values, selection.displayProperty);
|
|
4930
|
-
const
|
|
4999
|
+
const versionedKeys = data.versionedKeys;
|
|
5000
|
+
const excludedKeys = new Set((extras.excludedKeys ?? []).map((key) => createReferenceKey(key, versionedKeys)));
|
|
4931
5001
|
const keptValues = values.filter((value) => {
|
|
4932
|
-
const key = data.keyProperty
|
|
4933
|
-
|
|
5002
|
+
const key = data.keyProperty &&
|
|
5003
|
+
findReferenceKey(readValueSetProperty(value, data.keyProperty), versionedKeys);
|
|
5004
|
+
return key === undefined || !excludedKeys.has(key);
|
|
4934
5005
|
});
|
|
4935
|
-
const keys = new Set(keptValues.map((value) =>
|
|
5006
|
+
const keys = new Set(keptValues.map((value) => findReferenceKey(readValueSetProperty(value, keyProperty), versionedKeys)));
|
|
4936
5007
|
const extraValues = (extras.extras ?? [])
|
|
4937
5008
|
.map((extra) => extra.value)
|
|
4938
|
-
.filter((extra) =>
|
|
5009
|
+
.filter((extra) => {
|
|
5010
|
+
const key = findReferenceKey(extra.code, versionedKeys);
|
|
5011
|
+
if (key === undefined || keys.has(key)) {
|
|
5012
|
+
return false;
|
|
5013
|
+
}
|
|
5014
|
+
keys.add(key);
|
|
5015
|
+
return true;
|
|
5016
|
+
})
|
|
4939
5017
|
.map((extra) => {
|
|
4940
5018
|
const merged = {
|
|
4941
5019
|
...extra,
|
|
@@ -4953,8 +5031,9 @@ const mergedExtras = new WeakSet();
|
|
|
4953
5031
|
function isMergedExtra(value) {
|
|
4954
5032
|
return typeof value === "object" && value !== null && mergedExtras.has(value);
|
|
4955
5033
|
}
|
|
4956
|
-
|
|
4957
|
-
|
|
5034
|
+
/** The key as {@link createReferenceKey} gives it, undefined for no key. */
|
|
5035
|
+
function findReferenceKey(key, versionedKeys) {
|
|
5036
|
+
return key === undefined || key === null ? undefined : createReferenceKey(key, versionedKeys);
|
|
4958
5037
|
}
|
|
4959
5038
|
function createExplicitKey(module, namespace, name, branchUri) {
|
|
4960
5039
|
return JSON.stringify([module ?? "", namespace ?? "", name, branchUri ?? ""]);
|
|
@@ -8045,6 +8124,13 @@ class SmartFormService {
|
|
|
8045
8124
|
* mutating. See ADR-0007, decision 4.
|
|
8046
8125
|
*/
|
|
8047
8126
|
this.revision = this.revisionState.asReadonly();
|
|
8127
|
+
this.controlRevisionState = signal(0, /* @ts-ignore */
|
|
8128
|
+
...(ngDevMode ? [{ debugName: "controlRevisionState" }] : /* istanbul ignore next */ []));
|
|
8129
|
+
/**
|
|
8130
|
+
* Counts the value changes of the form's controls, which reach the data only at submit. A
|
|
8131
|
+
* widget showing the model as edited (the html widget) reads it to follow the user's edits.
|
|
8132
|
+
*/
|
|
8133
|
+
this.controlRevision = this.controlRevisionState.asReadonly();
|
|
8048
8134
|
this.emptyKey = 'e#m%p@t*y';
|
|
8049
8135
|
this.group = {};
|
|
8050
8136
|
/** The previous build's controls during a rebuild: a widget still shown keeps its control. */
|
|
@@ -8080,6 +8166,10 @@ class SmartFormService {
|
|
|
8080
8166
|
widgetsChanged() {
|
|
8081
8167
|
this.revisionState.update((revision) => revision + 1);
|
|
8082
8168
|
}
|
|
8169
|
+
/** Whether the set the selection widget lists has Versioned Keys. */
|
|
8170
|
+
hasVersionedKeys(widget) {
|
|
8171
|
+
return !!this.filledValueSets.get(widget)?.versionedKeys;
|
|
8172
|
+
}
|
|
8083
8173
|
/** Forgets the agreed values; the form is being rebuilt from the model. */
|
|
8084
8174
|
resetExchangedValues() {
|
|
8085
8175
|
this.exchanged.clear();
|
|
@@ -8318,8 +8408,9 @@ class SmartFormService {
|
|
|
8318
8408
|
}
|
|
8319
8409
|
/**
|
|
8320
8410
|
* The values of the set the widget's parent fields admit: each dependency compares the parent
|
|
8321
|
-
* control's value with the value object's property
|
|
8322
|
-
* every value. An extra of the view is listed only
|
|
8411
|
+
* control's value with the value object's property, by the Versioned Keys of the widget's set. A
|
|
8412
|
+
* parent with no control in the form admits every value. An extra of the view is listed only
|
|
8413
|
+
* while the parents hold the keys of its tag.
|
|
8323
8414
|
*/
|
|
8324
8415
|
narrowValueSetData(widget, valueSetData, form) {
|
|
8325
8416
|
const dependencies = this.getDependencies(widget);
|
|
@@ -8327,16 +8418,18 @@ class SmartFormService {
|
|
|
8327
8418
|
return valueSetData;
|
|
8328
8419
|
}
|
|
8329
8420
|
const keyProperty = this.readKeyProperty(widget, valueSetData);
|
|
8421
|
+
const versionedKeys = valueSetData.versionedKeys;
|
|
8330
8422
|
const extras = this.viewExtras.get(widget) ?? [];
|
|
8331
8423
|
const parentKeys = this.readDependencyKeys(widget, form);
|
|
8332
8424
|
const values = valueSetData.values.filter((value) => {
|
|
8425
|
+
const key = readValueSetProperty(value, keyProperty);
|
|
8333
8426
|
const sameKeyExtras = isMergedExtra(value)
|
|
8334
|
-
? extras.filter((extra) => isSameKey(extra.value.code,
|
|
8427
|
+
? extras.filter((extra) => isSameKey(extra.value.code, key, versionedKeys))
|
|
8335
8428
|
: [];
|
|
8336
8429
|
if (sameKeyExtras.length) {
|
|
8337
|
-
return sameKeyExtras.some((extra) => !!extra.tag && this.isTaggedWith(extra.tag, parentKeys));
|
|
8430
|
+
return sameKeyExtras.some((extra) => !!extra.tag && this.isTaggedWith(extra.tag, parentKeys, versionedKeys));
|
|
8338
8431
|
}
|
|
8339
|
-
return dependencies.every((dependency) => this.isAdmittedBy(dependency, value, form));
|
|
8432
|
+
return dependencies.every((dependency) => this.isAdmittedBy(dependency, value, form, versionedKeys));
|
|
8340
8433
|
});
|
|
8341
8434
|
return { ...valueSetData, values };
|
|
8342
8435
|
}
|
|
@@ -8349,17 +8442,17 @@ class SmartFormService {
|
|
|
8349
8442
|
: this.readParentKeys(dependency.modelPath, control.value);
|
|
8350
8443
|
});
|
|
8351
8444
|
}
|
|
8352
|
-
isTaggedWith(tag, parentKeys) {
|
|
8445
|
+
isTaggedWith(tag, parentKeys, versionedKeys) {
|
|
8353
8446
|
return (tag.length === parentKeys.length &&
|
|
8354
8447
|
tag.every((keys, index) => keys.length === parentKeys[index].length &&
|
|
8355
|
-
keys.every((key, keyIndex) => isSameKey(key, parentKeys[index][keyIndex]))));
|
|
8448
|
+
keys.every((key, keyIndex) => isSameKey(key, parentKeys[index][keyIndex], versionedKeys))));
|
|
8356
8449
|
}
|
|
8357
|
-
isAdmittedBy(dependency, value, form) {
|
|
8450
|
+
isAdmittedBy(dependency, value, form, versionedKeys) {
|
|
8358
8451
|
const control = form.controls[dependency.modelPath];
|
|
8359
8452
|
if (!control || isEmptyValue(control.value)) {
|
|
8360
8453
|
return !this.isBlockedByEmptyParent(dependency, form);
|
|
8361
8454
|
}
|
|
8362
|
-
return this.readParentKeys(dependency.modelPath, control.value).some((key) => isSameKey(key, value[dependency.property]));
|
|
8455
|
+
return this.readParentKeys(dependency.modelPath, control.value).some((key) => isSameKey(key, value[dependency.property], versionedKeys));
|
|
8363
8456
|
}
|
|
8364
8457
|
/**
|
|
8365
8458
|
* The keys a parent field holds: an object value, the whole entry of an OBJECT selection, gives
|
|
@@ -8371,7 +8464,7 @@ class SmartFormService {
|
|
|
8371
8464
|
return value;
|
|
8372
8465
|
}
|
|
8373
8466
|
const parent = this.findWidgetByKey(modelPath);
|
|
8374
|
-
return value
|
|
8467
|
+
return readValueSetProperty(value, valueSetKeyProperty([value], parent?.selection?.keyProperty));
|
|
8375
8468
|
});
|
|
8376
8469
|
}
|
|
8377
8470
|
/**
|
|
@@ -8403,7 +8496,7 @@ class SmartFormService {
|
|
|
8403
8496
|
}
|
|
8404
8497
|
const keyProperty = this.readKeyProperty(widget, valueSetData);
|
|
8405
8498
|
const readKey = this.createKeyReader(widget, valueSetData);
|
|
8406
|
-
const isAdmitted = (item) => valueSetData.values.some((admitted) => isSameKey(admitted
|
|
8499
|
+
const isAdmitted = (item) => valueSetData.values.some((admitted) => isSameKey(readValueSetProperty(admitted, keyProperty), readKey(item), valueSetData.versionedKeys));
|
|
8407
8500
|
if (Array.isArray(value)) {
|
|
8408
8501
|
return value.filter(isAdmitted);
|
|
8409
8502
|
}
|
|
@@ -8413,7 +8506,7 @@ class SmartFormService {
|
|
|
8413
8506
|
createKeyReader(widget, valueSetData) {
|
|
8414
8507
|
const keyProperty = this.readKeyProperty(widget, valueSetData);
|
|
8415
8508
|
return (item) => widget.selection.type === SelectionDefinitionTypeEnum.OBJECT
|
|
8416
|
-
? item
|
|
8509
|
+
? readValueSetProperty(item, keyProperty)
|
|
8417
8510
|
: item;
|
|
8418
8511
|
}
|
|
8419
8512
|
readKeyProperty(widget, valueSetData) {
|
|
@@ -8445,11 +8538,12 @@ class SmartFormService {
|
|
|
8445
8538
|
!selection.mapping?.length) {
|
|
8446
8539
|
let keyProperty = valueSetKeyProperty(values, selection.keyProperty ?? valueSetData?.keyProperty);
|
|
8447
8540
|
select.valueList = values.map((value) => {
|
|
8541
|
+
const key = readValueSetProperty(value, keyProperty);
|
|
8448
8542
|
return {
|
|
8449
|
-
key
|
|
8450
|
-
label: value
|
|
8543
|
+
key,
|
|
8544
|
+
label: readValueSetProperty(value, displayProperty),
|
|
8451
8545
|
type: SmartFormWidgetType.ITEM,
|
|
8452
|
-
value:
|
|
8546
|
+
value: key,
|
|
8453
8547
|
};
|
|
8454
8548
|
});
|
|
8455
8549
|
if (!isRequired(select)) {
|
|
@@ -8461,7 +8555,7 @@ class SmartFormService {
|
|
|
8461
8555
|
select.valueList = values.map((value, index) => {
|
|
8462
8556
|
return {
|
|
8463
8557
|
key: `${index}`,
|
|
8464
|
-
label: value
|
|
8558
|
+
label: readValueSetProperty(value, displayProperty),
|
|
8465
8559
|
type: SmartFormWidgetType.ITEM,
|
|
8466
8560
|
value: value,
|
|
8467
8561
|
};
|
|
@@ -8493,6 +8587,8 @@ class SmartFormService {
|
|
|
8493
8587
|
}
|
|
8494
8588
|
else {
|
|
8495
8589
|
this.formGroup = new UntypedFormGroup(this.group);
|
|
8590
|
+
// The group lives as long as this service: a rebuild reuses it, so one subscription holds.
|
|
8591
|
+
this.formGroup.valueChanges.subscribe(() => this.controlRevisionState.update((revision) => revision + 1));
|
|
8496
8592
|
}
|
|
8497
8593
|
if (this.smartForm.validators && this.smartForm.validators.length) {
|
|
8498
8594
|
this.formGroup.setValidators(this.smartForm.validators.map((validator) => {
|
|
@@ -8638,24 +8734,40 @@ class SmartFormService {
|
|
|
8638
8734
|
this.copyValueFromFormToWidgetBeans(this.smartForm.widgets, group);
|
|
8639
8735
|
const data = this.smartForm.componentModel?.data;
|
|
8640
8736
|
if (data) {
|
|
8641
|
-
|
|
8642
|
-
if (!group.controls[key]) {
|
|
8643
|
-
continue;
|
|
8644
|
-
}
|
|
8645
|
-
const agreed = this.exchanged.has(key)
|
|
8646
|
-
? this.exchanged.get(key)
|
|
8647
|
-
: this.getValueDeeply(data, key);
|
|
8648
|
-
const value = coerceToTypeOf(agreed, group.controls[key].value);
|
|
8649
|
-
if (this.exchanged.has(key) && deepEqual(value, agreed)) {
|
|
8650
|
-
continue;
|
|
8651
|
-
}
|
|
8652
|
-
this.setValueDeeply(data, key, value);
|
|
8653
|
-
this.exchanged.set(key, structuredClone(this.getValueDeeply(data, key)));
|
|
8654
|
-
}
|
|
8737
|
+
this.copyControlsIntoData(data, group, this.exchanged);
|
|
8655
8738
|
}
|
|
8656
8739
|
}
|
|
8657
8740
|
return this.smartForm;
|
|
8658
8741
|
}
|
|
8742
|
+
/**
|
|
8743
|
+
* The form's data with the controls' edits applied as the next submit would copy them (a
|
|
8744
|
+
* MATRIX widget excepted: its submit changes the data through the widget beans). The result is
|
|
8745
|
+
* read-only: it is a copy, but the live data itself when the form has no controls; undefined
|
|
8746
|
+
* without a data.
|
|
8747
|
+
*/
|
|
8748
|
+
buildModelWithEdits() {
|
|
8749
|
+
const data = this.smartForm?.componentModel?.data;
|
|
8750
|
+
if (!data || !this.formGroup) {
|
|
8751
|
+
return data;
|
|
8752
|
+
}
|
|
8753
|
+
const model = structuredClone(data);
|
|
8754
|
+
this.copyControlsIntoData(model, this.formGroup, new Map(this.exchanged));
|
|
8755
|
+
return model;
|
|
8756
|
+
}
|
|
8757
|
+
copyControlsIntoData(data, group, exchanged) {
|
|
8758
|
+
for (const key of this.getPropertyNamesDeeply(data)) {
|
|
8759
|
+
if (!group.controls[key]) {
|
|
8760
|
+
continue;
|
|
8761
|
+
}
|
|
8762
|
+
const agreed = exchanged.has(key) ? exchanged.get(key) : this.getValueDeeply(data, key);
|
|
8763
|
+
const value = coerceToTypeOf(agreed, group.controls[key].value);
|
|
8764
|
+
if (exchanged.has(key) && deepEqual(value, agreed)) {
|
|
8765
|
+
continue;
|
|
8766
|
+
}
|
|
8767
|
+
this.setValueDeeply(data, key, value);
|
|
8768
|
+
exchanged.set(key, structuredClone(this.getValueDeeply(data, key)));
|
|
8769
|
+
}
|
|
8770
|
+
}
|
|
8659
8771
|
getModel(group) {
|
|
8660
8772
|
if (!this.smartForm?.componentModel) {
|
|
8661
8773
|
return {};
|
|
@@ -10644,9 +10756,10 @@ function injectSmartHtmlRenderer(container) {
|
|
|
10644
10756
|
|
|
10645
10757
|
/**
|
|
10646
10758
|
* The HTML widget: server-sent HTML whose client placeholders are resolved against the model of
|
|
10647
|
-
* the form it sits on, the widget's `root` applied. It
|
|
10648
|
-
* its own; it follows the form's revision
|
|
10649
|
-
* rewrites the html only when the
|
|
10759
|
+
* the form it sits on, its controls' unsubmitted edits included, the widget's `root` applied. It
|
|
10760
|
+
* has no control, no label and no value of its own; it follows the form's revision, every edit of
|
|
10761
|
+
* a control and the value sets it reads and re-resolves, which rewrites the html only when the
|
|
10762
|
+
* result differs. See HTML-WIDGET.md.
|
|
10650
10763
|
*/
|
|
10651
10764
|
class SmartHtmlWidgetComponent {
|
|
10652
10765
|
constructor() {
|
|
@@ -10659,13 +10772,14 @@ class SmartHtmlWidgetComponent {
|
|
|
10659
10772
|
...(ngDevMode ? [{ debugName: "widgetInstance" }] : /* istanbul ignore next */ []));
|
|
10660
10773
|
effect(() => {
|
|
10661
10774
|
this.service.revision();
|
|
10775
|
+
this.service.controlRevision();
|
|
10662
10776
|
this.valueSetReads.changed();
|
|
10663
10777
|
const properties = this.widgetInstance().htmlProperties;
|
|
10664
10778
|
const model = this.service.smartForm?.componentModel;
|
|
10665
10779
|
this.warnUnloaded(properties);
|
|
10666
10780
|
this.renderer.useCss(properties?.css);
|
|
10667
10781
|
this.renderer.render(properties?.html ?? '', {
|
|
10668
|
-
data: this.scope(
|
|
10782
|
+
data: this.scope(this.service.buildModelWithEdits(), properties?.root, properties?.html),
|
|
10669
10783
|
findValueSetData: this.valueSetReads.track(createValueSetLookup(model?.valueSets, this.valueSetStore, this.smartComponent?.uuid)),
|
|
10670
10784
|
icons: properties?.icons,
|
|
10671
10785
|
});
|
|
@@ -11230,6 +11344,16 @@ class SmartformwidgetComponent {
|
|
|
11230
11344
|
this.addOnBlur = true;
|
|
11231
11345
|
this.separatorKeysCodes = [ENTER, COMMA];
|
|
11232
11346
|
this.emptySeparatorKeysCodes = [];
|
|
11347
|
+
/**
|
|
11348
|
+
* The mat-select comparison of the widget's keys, by the Versioned Keys of its set. An arrow
|
|
11349
|
+
* property, as the template hands it to mat-select unbound.
|
|
11350
|
+
*/
|
|
11351
|
+
this.compareItems = (o1, o2) => {
|
|
11352
|
+
const versionedKeys = this.service.hasVersionedKeys(this.widgetInstance);
|
|
11353
|
+
const v1 = o1 && typeof o1 === 'string' ? createReferenceKey(o1, versionedKeys) : o1;
|
|
11354
|
+
const v2 = o2 && typeof o2 === 'string' ? createReferenceKey(o2, versionedKeys) : o2;
|
|
11355
|
+
return deepEqual(v1, v2);
|
|
11356
|
+
};
|
|
11233
11357
|
// The form changes the widget objects it has already handed to this template — values,
|
|
11234
11358
|
// constraints, the touched state of the controls — and a mutation of a plain object is
|
|
11235
11359
|
// invisible to change detection. Registering here rather than being found by a view query
|
|
@@ -12076,19 +12200,6 @@ class SmartformwidgetComponent {
|
|
|
12076
12200
|
}
|
|
12077
12201
|
this.setDateFormControl(control, yearMonthValue, '00:00');
|
|
12078
12202
|
}
|
|
12079
|
-
compareItems(o1, o2) {
|
|
12080
|
-
function convert(obj) {
|
|
12081
|
-
if (obj && typeof obj === 'string') {
|
|
12082
|
-
return obj.replace(/\.v[0-9]+$/, '');
|
|
12083
|
-
}
|
|
12084
|
-
return obj;
|
|
12085
|
-
}
|
|
12086
|
-
// calling this.truncateUri didn't work
|
|
12087
|
-
// TODO: use widget.selection.parameter for ignoreUriVersion / strictUriComparison;
|
|
12088
|
-
let v1 = convert(o1);
|
|
12089
|
-
let v2 = convert(o2);
|
|
12090
|
-
return deepEqual(v1, v2);
|
|
12091
|
-
}
|
|
12092
12203
|
truncateUri(uri) {
|
|
12093
12204
|
if (uri && typeof uri === 'string') {
|
|
12094
12205
|
return uri.replace(/\.v[0-9]+$/, '');
|