@smartbit4all/ng-client 7.3.36 → 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.
@@ -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) => String(value[keyProperty]) === String(code));
4339
- return hit?.[valueSetDisplayProperty(values)];
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
- return candidates.find((c) => c in values[0]) ?? candidates[candidates.length - 1];
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
- /** Whether two keys are equal; compared as text, as a control holds a boolean key as text. */
4352
- function isSameKey(key1, key2) {
4353
- return String(key1) === String(key2);
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
- /** The held extras followed by the received ones of a code not held yet. */
4915
- function addReceivedExtras(held, received) {
4916
- const heldCodes = new Set((held ?? []).map((extra) => extra.value.code));
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 text at the set's key property is an excluded key, then the
4924
- * view's extras keyed by their code under the selection's key property, none the set has.
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 excludedKeys = new Set(extras.excludedKeys ?? []);
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 ? value?.[data.keyProperty] : undefined;
4933
- return key === undefined || key === null || !excludedKeys.has(String(key));
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) => convertKeyToText(value?.[keyProperty])));
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) => extra.code !== undefined && !keys.has(extra.code))
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
- function convertKeyToText(key) {
4957
- return key === undefined || key === null ? undefined : String(key);
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. A parent with no control in the form admits
8322
- * every value. An extra of the view is listed only while the parents hold the keys of its tag.
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, value[keyProperty]))
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[valueSetKeyProperty([value], parent?.selection?.keyProperty)];
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[keyProperty], readKey(item)));
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?.[keyProperty]
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: value[keyProperty],
8450
- label: value[displayProperty],
8543
+ key,
8544
+ label: readValueSetProperty(value, displayProperty),
8451
8545
  type: SmartFormWidgetType.ITEM,
8452
- value: value[keyProperty],
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[displayProperty],
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
- for (const key of this.getPropertyNamesDeeply(data)) {
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 {};
@@ -9075,11 +9187,11 @@ class SmartfileuploaderComponent {
9075
9187
  };
9076
9188
  }
9077
9189
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9078
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--warninig-color: #be2d2e;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warninig-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warninig-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warninig-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warninig-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }] }); }
9190
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartfileuploaderComponent, isStandalone: true, selector: "smartfileuploader", inputs: { uploadCallback: { classPropertyName: "uploadCallback", publicName: "uploadCallback", isSignal: true, isRequired: true, transformFunction: null }, fileFormats: { classPropertyName: "fileFormats", publicName: "fileFormats", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: false, isRequired: false, transformFunction: null }, useIconButton: { classPropertyName: "useIconButton", publicName: "useIconButton", isSignal: true, isRequired: false, transformFunction: null }, isMultiple: { classPropertyName: "isMultiple", publicName: "isMultiple", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }] }); }
9079
9191
  }
9080
9192
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartfileuploaderComponent, decorators: [{
9081
9193
  type: Component,
9082
- args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--warninig-color: #be2d2e;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warninig-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warninig-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warninig-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warninig-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"] }]
9194
+ args: [{ selector: 'smartfileuploader', imports: [NgClass, MatLabel, MatIcon, MatButton, UiActionButtonComponent, SmartIconComponent], template: "<div\n class=\"container\"\n [ngClass]=\"{ disabledWidget: isDisabled() }\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n [class.dragover]=\"isDragOver\"\n>\n <div class=\"fileContainer\" (click)=\"fileInput.click()\">\n <input\n #fileInput\n id=\"addFile\"\n placeholder=\"fileInput\"\n type=\"file\"\n (change)=\"getFile($event)\"\n class=\"file\"\n accept=\"{{ fileFormats()?.join(', ') }}\"\n [size]=\"maxSizeMb()\"\n [multiple]=\"isMultiple()\"\n [disabled]=\"isDisabled()\"\n style=\"display: none\"\n />\n <div class=\"fileUploadContentContainer\">\n <div class=\"icon\">\n @if (useIconButton()) {\n <mat-label class=\"addFileButton\">\n <mat-icon fontSet=\"material-symbols-outlined\" color=\"primary\" class=\"addCircle\"\n >add_circle</mat-icon\n >\n </mat-label>\n }\n @if (!useIconButton()) {\n <button mat-raised-button color=\"primary\" class=\"addFileButton\">\n {{ i18n!.addFile }}\n </button>\n }\n </div>\n <div class=\"labels\">\n @if (useIconButton()) {\n <mat-label class=\"label primary title\">\n {{ i18n!.addFile }}\n </mat-label>\n }\n <mat-label class=\"label secondary\"> {{ i18n!.browseOrDrag }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.maxSize }} </mat-label>\n <mat-label class=\"subLabel primary\"> {{ i18n!.formats }} </mat-label>\n </div>\n @if (files.length) {\n <div class=\"uploadButton\">\n <ui-action-button\n (actionClick)=\"uploadFile($event)\"\n [code]=\"'default-upload'\"\n [descriptor]=\"uploadButton\"\n >\n </ui-action-button>\n </div>\n }\n </div>\n </div>\n\n @if (errors.length) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"'error'\" [color]=\"'warn'\"></smart-icon>\n <div class=\"errors\">\n @for (error of errors; track error) {\n <span>{{ error }}</span>\n }\n </div>\n </div>\n }\n\n @if (files.length) {\n <div class=\"uploadedFilesContainer\">\n @for (file of files; track file; let i = $index) {\n <div class=\"uploadedFile\">\n @if (filePreviewUrl(file)) {\n <img [src]=\"filePreviewUrl(file)\" [alt]=\"file.name\" width=\"50\" height=\"50\" />\n } @else {\n <smart-icon [icon]=\"'insert_drive_file'\"></smart-icon>\n }\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file.name }}</span>\n <span class=\"fileSize\">{{ formatSize(file.size) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file)\"\n [attr.data-testid]=\"'default_download'\"\n ></smart-icon>\n <smart-icon\n class=\"removeIcon\"\n icon=\"delete\"\n [attr.data-testid]=\"'default_remove'\"\n (click)=\"remove(i)\"\n ></smart-icon>\n </div>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem}.container{display:flex;flex-direction:column;border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileContainer{cursor:pointer;padding:1rem;display:flex;flex-direction:column}.fileContainer:hover{background-color:rgb(from var(--border-color) r g b / .1)}.disabledWidget ::ng-deep .fileContainer:hover{background-color:unset;cursor:unset}.disabledWidget ::ng-deep *{opacity:.85}.dragover{background-color:rgb(from var(--border-color) r g b / .1)}.fileUploadContentContainer{display:flex;flex-direction:row;gap:1rem}.icon mat-icon{font-size:3rem;width:unset;height:unset}.icon{align-content:center}.labels{flex:1;display:flex;flex-direction:column;justify-content:center;width:fit-content}.uploadButton{align-content:center}.primary{color:var(--primary-color)}.secondary{color:var(--gray)}.label{margin:unset}.subLabel{font-size:smaller}.title{padding-bottom:.3rem}.errorMessage,.uploadedFilesContainer{border-top:2px solid var(--border-color)}.errorMessage{display:flex;flex-direction:row;align-items:center;color:var(--warn-color);padding:.5rem 1rem;gap:1rem}.errorMessage ::ng-deep mat-icon{color:var(--warn-color)}.errors{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0;gap:1rem}.uploadedFile:last-child{border-bottom:none}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}\n"] }]
9083
9195
  }], propDecorators: { fileInput: [{
9084
9196
  type: ViewChild,
9085
9197
  args: ['fileInput']
@@ -9620,6 +9732,7 @@ class SmartFileEditorComponent {
9620
9732
  // ---- ControlValueAccessor implementation ----
9621
9733
  this.onChange = (value) => { };
9622
9734
  this.onTouched = () => { };
9735
+ /** The editor's disabled state: the widget's at creation, then the form control's. */
9623
9736
  this.isDisabled = false;
9624
9737
  }
9625
9738
  get widgetInstance() {
@@ -9635,6 +9748,7 @@ class SmartFileEditorComponent {
9635
9748
  typeof structuredClone === 'function'
9636
9749
  ? structuredClone(value)
9637
9750
  : JSON.parse(JSON.stringify(value));
9751
+ this.isDisabled = !!value.isDisabled;
9638
9752
  if (!this._widgetInstance.fileUploaderProperties?.undoIcon) {
9639
9753
  this._widgetInstance.fileUploaderProperties = {
9640
9754
  ...(this._widgetInstance.fileUploaderProperties ?? {}),
@@ -9658,6 +9772,17 @@ class SmartFileEditorComponent {
9658
9772
  });
9659
9773
  this.onChange(this._widgetInstance.value);
9660
9774
  this.fileToChange = undefined;
9775
+ this.clearedFile = undefined;
9776
+ }
9777
+ clear() {
9778
+ this.clearedFile = this._widgetInstance.value;
9779
+ this._widgetInstance.value = null;
9780
+ this.onChange(null);
9781
+ }
9782
+ undoClear() {
9783
+ this._widgetInstance.value = this.clearedFile;
9784
+ this.onChange(this.clearedFile);
9785
+ this.clearedFile = undefined;
9661
9786
  }
9662
9787
  change(file) {
9663
9788
  this.fileToChange = file;
@@ -9701,6 +9826,10 @@ class SmartFileEditorComponent {
9701
9826
  writeValue(value) {
9702
9827
  if (value !== undefined && this._widgetInstance) {
9703
9828
  this._widgetInstance.value = value;
9829
+ // A file written over a clear makes the undo obsolete; a written null or '' is the clear itself.
9830
+ if (value) {
9831
+ this.clearedFile = undefined;
9832
+ }
9704
9833
  this.cdr.markForCheck();
9705
9834
  }
9706
9835
  }
@@ -9712,8 +9841,17 @@ class SmartFileEditorComponent {
9712
9841
  }
9713
9842
  setDisabledState(isDisabled) {
9714
9843
  this.isDisabled = isDisabled;
9844
+ // A disable ends the editing (Save or Discard), so a clear is no longer undoable.
9845
+ if (isDisabled) {
9846
+ this.clearedFile = undefined;
9847
+ }
9848
+ this.cdr.markForCheck();
9715
9849
  }
9716
9850
  // ---- UI getters ----
9851
+ /** An absent clearEnabled offers the clear, as its default in the form definition. */
9852
+ get isClearEnabled() {
9853
+ return this._widgetInstance.fileUploaderProperties?.clearEnabled !== false;
9854
+ }
9717
9855
  get fileIcon() {
9718
9856
  return 'insert_drive_file';
9719
9857
  }
@@ -9730,6 +9868,17 @@ class SmartFileEditorComponent {
9730
9868
  };
9731
9869
  return changeDescriptor;
9732
9870
  }
9871
+ /** The delete icon's own descriptor: nothing of the change or upload action, so no tooltip. */
9872
+ get deleteButton() {
9873
+ var changeButton = this.changeButton;
9874
+ return {
9875
+ title: '',
9876
+ color: 'warn',
9877
+ type: changeButton.type,
9878
+ icon: 'delete',
9879
+ iconPosition: changeButton.iconPosition,
9880
+ };
9881
+ }
9733
9882
  get removeButton() {
9734
9883
  var changeButton = this.changeButton;
9735
9884
  return {
@@ -9750,7 +9899,7 @@ class SmartFileEditorComponent {
9750
9899
  useExisting: forwardRef(() => SmartFileEditorComponent),
9751
9900
  multi: true,
9752
9901
  },
9753
- ], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warninig-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"false\"\n [isDisabled]=\"widgetInstance.isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: widgetInstance.isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"widgetInstance.isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--warninig-color: #be2d2e;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep mat-icon{color:var(--warninig-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warninig-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warninig-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
9902
+ ], ngImport: i0, template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: UiActionButtonComponent, selector: "ui-action-button", inputs: ["disabled", "descriptor", "code", "identifier", "viewActivated", "tab", "rowState", "addedCssClass", "addBasicClasses", "iconPlaceholder", "chords"], outputs: ["actionClick", "actionDoubleClick"] }] }); }
9754
9903
  }
9755
9904
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFileEditorComponent, decorators: [{
9756
9905
  type: Component,
@@ -9760,7 +9909,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
9760
9909
  useExisting: forwardRef(() => SmartFileEditorComponent),
9761
9910
  multi: true,
9762
9911
  },
9763
- ], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warninig-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"false\"\n [isDisabled]=\"widgetInstance.isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: widgetInstance.isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"widgetInstance.isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--warninig-color: #be2d2e;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep mat-icon{color:var(--warninig-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warninig-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warninig-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"] }]
9912
+ ], imports: [NgClass, NgStyle, SmartIconComponent, UploadWidgetComponent, UiActionToolbarComponent, UiActionButtonComponent], template: "@if (widgetInstance && widgetInstance.fileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else if (!widgetInstance.value || fileToChange) {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.fileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"false\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n @if (clearedFile && !isDisabled) {\n <div class=\"undoClearContainer\">\n <ui-action-button\n class=\"removeIcon undoClearButton\"\n (actionClick)=\"undoClear()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n </div>\n }\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ widgetInstance.value?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(widgetInstance.value?.size)) }}</span>\n </div>\n </div>\n\n <div class=\"fileActions\">\n <smart-ui-action-toolbar [id]=\"widgetInstance.toolbarId\"></smart-ui-action-toolbar>\n\n @if (!fileToChange) {\n <ui-action-button\n class=\"changeButton\"\n (actionClick)=\"change(widgetInstance.value)\"\n [disabled]=\"isDisabled\"\n [descriptor]=\"changeButton\"\n >\n </ui-action-button>\n @if (!isDisabled && isClearEnabled) {\n <ui-action-button\n class=\"deleteButton\"\n (actionClick)=\"clear()\"\n [descriptor]=\"deleteButton\"\n ></ui-action-button>\n }\n } @else {\n <ui-action-button\n class=\"removeIcon\"\n (actionClick)=\"cancelChange()\"\n [descriptor]=\"removeButton\"\n ></ui-action-button>\n }\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius)}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.fileEditorComponent:has(::ng-deep smartfileuploader) ::ng-deep .uploadedFile{border-top:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button,.undoClearContainer ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover,.undoClearContainer ::ng-deep button:hover{border:unset}.undoClearContainer{display:flex;justify-content:flex-end;padding:0 1rem .5rem}.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 12px,#f1f1f1 12px 24px)}\n"] }]
9764
9913
  }], propDecorators: { widgetInstance: [{
9765
9914
  type: Input
9766
9915
  }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }] } });
@@ -10607,9 +10756,10 @@ function injectSmartHtmlRenderer(container) {
10607
10756
 
10608
10757
  /**
10609
10758
  * The HTML widget: server-sent HTML whose client placeholders are resolved against the model of
10610
- * the form it sits on, the widget's `root` applied. It has no control, no label and no value of
10611
- * its own; it follows the form's revision and the value sets it reads and re-resolves, which
10612
- * rewrites the html only when the result differs. See HTML-WIDGET.md.
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.
10613
10763
  */
10614
10764
  class SmartHtmlWidgetComponent {
10615
10765
  constructor() {
@@ -10622,13 +10772,14 @@ class SmartHtmlWidgetComponent {
10622
10772
  ...(ngDevMode ? [{ debugName: "widgetInstance" }] : /* istanbul ignore next */ []));
10623
10773
  effect(() => {
10624
10774
  this.service.revision();
10775
+ this.service.controlRevision();
10625
10776
  this.valueSetReads.changed();
10626
10777
  const properties = this.widgetInstance().htmlProperties;
10627
10778
  const model = this.service.smartForm?.componentModel;
10628
10779
  this.warnUnloaded(properties);
10629
10780
  this.renderer.useCss(properties?.css);
10630
10781
  this.renderer.render(properties?.html ?? '', {
10631
- data: this.scope(model?.data, properties?.root, properties?.html),
10782
+ data: this.scope(this.service.buildModelWithEdits(), properties?.root, properties?.html),
10632
10783
  findValueSetData: this.valueSetReads.track(createValueSetLookup(model?.valueSets, this.valueSetStore, this.smartComponent?.uuid)),
10633
10784
  icons: properties?.icons,
10634
10785
  });
@@ -10672,6 +10823,7 @@ class SmartMultiFileEditorComponent {
10672
10823
  // ---- ControlValueAccessor implementation ----
10673
10824
  this.onChange = (value) => { };
10674
10825
  this.onTouched = () => { };
10826
+ /** The editor's disabled state: the widget's at creation, then the form control's. */
10675
10827
  this.isDisabled = false;
10676
10828
  }
10677
10829
  get widgetInstance() {
@@ -10687,6 +10839,7 @@ class SmartMultiFileEditorComponent {
10687
10839
  typeof structuredClone === 'function'
10688
10840
  ? structuredClone(value)
10689
10841
  : JSON.parse(JSON.stringify(value));
10842
+ this.isDisabled = !!value.isDisabled;
10690
10843
  this.cdr.markForCheck();
10691
10844
  }
10692
10845
  upload(event) {
@@ -10773,6 +10926,7 @@ class SmartMultiFileEditorComponent {
10773
10926
  }
10774
10927
  setDisabledState(isDisabled) {
10775
10928
  this.isDisabled = isDisabled;
10929
+ this.cdr.markForCheck();
10776
10930
  }
10777
10931
  // ---- UI getters ----
10778
10932
  get removeIcon() {
@@ -10791,7 +10945,7 @@ class SmartMultiFileEditorComponent {
10791
10945
  useExisting: forwardRef(() => SmartMultiFileEditorComponent),
10792
10946
  multi: true,
10793
10947
  },
10794
- ], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warninig-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"true\"\n [isDisabled]=\"widgetInstance.isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: widgetInstance.isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"!isDisabled && file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--warninig-color: #be2d2e;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warninig-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warninig-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warninig-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .removeIcon *{opacity:.4!important;pointer-events:none!important}:host ::ng-deep .disabledWidget ::ng-deep .changeButton:hover,:host ::ng-deep .disabledWidget ::ng-deep .removeIcon:hover{cursor:not-allowed}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }] }); }
10948
+ ], ngImport: i0, template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: UiActionToolbarComponent, selector: "smart-ui-action-toolbar", inputs: ["uiActionModels", "uiActionDescriptorService", "id", "executor", "widgetId", "nodeId", "actionParams", "scrollOnWrap", "toolbarPropertes"], outputs: ["actionFired"] }, { kind: "component", type: SmartIconComponent, selector: "smart-icon", inputs: ["icon", "color", "imageResource"] }, { kind: "component", type: UploadWidgetComponent, selector: "smart-upload-widget", inputs: ["uploadDescriptor", "isMultiple", "isDisabled"], outputs: ["uploadFilesEvent"] }] }); }
10795
10949
  }
10796
10950
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartMultiFileEditorComponent, decorators: [{
10797
10951
  type: Component,
@@ -10801,7 +10955,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
10801
10955
  useExisting: forwardRef(() => SmartMultiFileEditorComponent),
10802
10956
  multi: true,
10803
10957
  },
10804
- ], imports: [NgClass, NgStyle, UiActionToolbarComponent, SmartIconComponent, UploadWidgetComponent], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warninig-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"true\"\n [isDisabled]=\"widgetInstance.isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: widgetInstance.isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"!isDisabled && file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--warninig-color: #be2d2e;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warninig-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warninig-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warninig-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .changeButton *,:host ::ng-deep .disabledWidget ::ng-deep .removeIcon *{opacity:.4!important;pointer-events:none!important}:host ::ng-deep .disabledWidget ::ng-deep .changeButton:hover,:host ::ng-deep .disabledWidget ::ng-deep .removeIcon:hover{cursor:not-allowed}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"] }]
10958
+ ], imports: [NgClass, NgStyle, UiActionToolbarComponent, SmartIconComponent, UploadWidgetComponent], template: "@if (widgetInstance && widgetInstance.multiFileUploaderProperties) {\n @if (widgetInstance.showLabel) {\n <h4\n class=\"labelContainer {{ widgetInstance.cssLabelClass ?? '' }}\"\n [ngClass]=\"getLabelNgClass()\"\n [ngStyle]=\"getLabelStyle()\"\n >\n {{ widgetInstance.label }}\n </h4>\n }\n <div class=\"fileEditorComponent\">\n @if (widgetInstance.value && !isTypeSupported(widgetInstance.value)) {\n <div class=\"errorMessage\">\n <smart-icon [icon]=\"errorIcon\" [color]=\"'var(--warn-color)'\"></smart-icon>\n <span> Unsupported widgetInstance.value type for this widget.</span>\n </div>\n } @else {\n <smart-upload-widget\n class=\"widgetContent {{ widgetInstance.cssClass ?? '' }}\"\n [ngClass]=\"getNgClass()\"\n [ngStyle]=\"getStyle()\"\n [uploadDescriptor]=\"widgetInstance.multiFileUploaderProperties.uploadDescriptor!\"\n [isMultiple]=\"true\"\n [isDisabled]=\"isDisabled\"\n (uploadFilesEvent)=\"upload($event)\"\n />\n }\n\n <div\n [ngClass]=\"{ disabledWidget: isDisabled }\"\n class=\"uploadedFileContainer\"\n [style.display]=\"\n widgetInstance.value && isTypeSupported(widgetInstance.value) ? 'block' : 'none'\n \"\n >\n <div class=\"scrollable\">\n @for (file of widgetInstance.value; track file; let i = $index) {\n <div class=\"uploadedFile\">\n <smart-icon class=\"uploadedFileIcon\" [icon]=\"fileIcon\"></smart-icon>\n <div class=\"fileData\">\n <div class=\"fileDataContainer\">\n <span class=\"fileName\">{{ file?.fileName }}</span>\n <span class=\"fileSize\">{{ formatSize($safeNavigationMigration(file?.size)) }}</span>\n </div>\n </div>\n <div class=\"fileActions\">\n <!--\n One toolbar per file, each resolving the widget's actions for itself and\n carrying its own file. The hidden sibling whose list these used to read is\n gone with the collection machinery (ADR-0006).\n -->\n <smart-ui-action-toolbar\n [id]=\"widgetInstance.toolbarId\"\n [actionParams]=\"{ _dataUri: file.dataUri }\"\n ></smart-ui-action-toolbar>\n <smart-icon\n class=\"downloadIcon\"\n icon=\"download\"\n (click)=\"downloadFile(file.dataUri)\"\n ></smart-icon>\n @if (!file.readOnly && !isDisabled) {\n <smart-icon\n class=\"removeIcon\"\n [icon]=\"removeIcon\"\n (click)=\"file.readOnly === false && deleteFile(file.dataUri)\"\n ></smart-icon>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n}\n", styles: [":host{--border-color: #a6aabd60;--def-border-radius: .5rem;width:100%}.fileEditorComponent{border:2px dashed var(--border-color);border-radius:var(--def-border-radius);overflow:hidden}.fileEditorComponent ::ng-deep smartfileuploader ::ng-deep .container{border:unset;border-radius:unset}.uploadedFileContainer{display:flex;flex-direction:column;border-top:1px solid #e0e0e0}.uploadedFile{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;padding:1rem;gap:1rem}.scrollable{max-height:30vh;overflow-y:auto}.scrollable>div:not(:last-child){border-bottom:1px solid #e0e0e0}.fileSize{display:flex;flex-direction:row;font-size:smaller}.fileData{flex:1;display:flex;flex-direction:column}.fileDataContainer{display:flex;flex-direction:column;justify-content:flex-end;width:fit-content}.fileActions{display:flex;flex-direction:row;justify-content:flex-end;gap:.5rem}.fileActions ::ng-deep button{border:unset}.fileActions ::ng-deep button:hover{border:unset}.removeIcon ::ng-deep i,.removeIcon ::ng-deep mat-icon{color:var(--warn-color)}.removeIcon ::ng-deep mat-icon,.downloadIcon ::ng-deep mat-icon{font-size:2rem}.removeIcon:hover,.downloadIcon:hover{cursor:pointer}smart-icon{display:flex;align-items:center}smart-icon ::ng-deep mat-icon{font-size:3rem;width:unset;height:unset}.uploadedFileIcon{width:3rem;display:flex;justify-content:center}.fileEditorComponent:has(::ng-deep .errorMessage){border-color:var(--warn-color)}.errorMessage{padding:1rem;font-size:1.5rem;color:var(--warn-color);display:flex;flex-direction:row;align-items:center;gap:1rem}:host ::ng-deep .disabledWidget ::ng-deep .fileData *{opacity:.7}:host ::ng-deep smartfileuploader ::ng-deep .uploadedFile{background:repeating-linear-gradient(45deg,#fafafa,#fafafa 6px,#f1f1f1 6px 12px)}\n"] }]
10805
10959
  }], propDecorators: { widgetInstance: [{
10806
10960
  type: Input
10807
10961
  }], uploadFilesEvent: [{ type: i0.Output, args: ["uploadFilesEvent"] }], fileEvent: [{ type: i0.Output, args: ["fileEvent"] }] } });
@@ -11190,6 +11344,16 @@ class SmartformwidgetComponent {
11190
11344
  this.addOnBlur = true;
11191
11345
  this.separatorKeysCodes = [ENTER, COMMA];
11192
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
+ };
11193
11357
  // The form changes the widget objects it has already handed to this template — values,
11194
11358
  // constraints, the touched state of the controls — and a mutation of a plain object is
11195
11359
  // invisible to change detection. Registering here rather than being found by a view query
@@ -12036,19 +12200,6 @@ class SmartformwidgetComponent {
12036
12200
  }
12037
12201
  this.setDateFormControl(control, yearMonthValue, '00:00');
12038
12202
  }
12039
- compareItems(o1, o2) {
12040
- function convert(obj) {
12041
- if (obj && typeof obj === 'string') {
12042
- return obj.replace(/\.v[0-9]+$/, '');
12043
- }
12044
- return obj;
12045
- }
12046
- // calling this.truncateUri didn't work
12047
- // TODO: use widget.selection.parameter for ignoreUriVersion / strictUriComparison;
12048
- let v1 = convert(o1);
12049
- let v2 = convert(o2);
12050
- return deepEqual(v1, v2);
12051
- }
12052
12203
  truncateUri(uri) {
12053
12204
  if (uri && typeof uri === 'string') {
12054
12205
  return uri.replace(/\.v[0-9]+$/, '');