@smartbit4all/ng-client 7.2.4 → 7.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/HTML-WIDGET.md CHANGED
@@ -38,6 +38,7 @@ A `DIV` widget változatlanul megmarad; a HTML widget annak a használatnak a re
38
38
  | `root` | dot-path a kontextus `data`-jához képest, amihez minden helyőrző relatív; **objektumot** nevez; üres = a `data` maga |
39
39
  | `fields` | a widget által olvasott abszolút pathok — a builder származtatja a helyőrzőkből, a kliens **nem** olvassa |
40
40
  | `css` | a widget stíluslapja, a html mellett utazik — lásd *Stíluslap* |
41
+ | `icons` | az **ikon-szótár**: `kulcs → ImageResource`, amit az ikon-slotok `data-sb4-icon-key`-jel címeznek — lásd *Ikon-slot* |
41
42
 
42
43
  A **feloldási kontextus** nézeten a `ComponentModel` (`data` + `valueSets`), grid-kártyán a sor
43
44
  (`GridRow.data`) a gridet mutató oldal `valueSets`-ével. Ha a `root` listát, egyetlen értéket vagy
@@ -55,6 +56,20 @@ layoutBuilder(view).vForm(f -> f
55
56
  vagy template-kulcsból (`h.template(ctx, "CONTACT_ENTRY_LIST")`): a kulcs a szerveren oldódik fel,
56
57
  a dróton nem utazik.
57
58
 
59
+ A html sima szöveg, tehát a `Tag` builder helyett **text blockként** is írható — így a sablon
60
+ ugyanúgy néz ki, ahogy a kliens megkapja:
61
+
62
+ ```java
63
+ .html("""
64
+ <div class="card">
65
+ <smart-icon data-sb4-icon="{{typeIcon}}"></smart-icon>
66
+ <b>{{name}}</b>
67
+ </div>""")
68
+ ```
69
+
70
+ A `html(String, Object...)` `String.format`-tal illeszti be az argumentumokat (akciókód,
71
+ toolbar-id), ilyenkor a szövegben a `%` `%%`.
72
+
58
73
  ## Stíluslap
59
74
 
60
75
  A widget a kinézetét is hozhatja: a `css` mezőben egy CSS-szöveget, amit a kliens a dokumentum
@@ -217,7 +232,9 @@ Logika-mentes Mustache-részhalmaz. Ugyanezt a nyelvtant olvassa a Java-oldali
217
232
 
218
233
  - Hiányzó, `null` vagy `null`-on átvezető path → **üres string, csendben**.
219
234
  - Primitív → `String(v)` (`false` → `false`, `0` → `0`).
220
- - Objektum vagy lista `{{p}}`-ként → üres + `console.warn`.
235
+ - Objektum vagy lista `{{p}}`-ként → üres + `console.warn`. Egyetlen kivétel: egy **ikon-slot**
236
+ név-attribútumának teljes értékeként az objektum az ikon `ImageResource`-a lesz — lásd
237
+ *Ikon-slot*.
221
238
  - Minden behelyettesített érték HTML-escape (`& < > " '`). URL-attribútumban (`href="{{url}}"`)
222
239
  nincs séma-szűrés: az érték a szerver saját modelljéből jön, a szerző felel érte.
223
240
 
@@ -300,7 +317,9 @@ escape-pel), a pipe-ok balról jobbra láncolhatók, a kimenetük is escape-en m
300
317
 
301
318
  | Hol | Mi történik |
302
319
  |---|---|
303
- | Java-builder (`build()`) | `IllegalArgumentException`: párosítatlan / keresztezett szekció, raw tag, érvénytelen path vagy `root`, a `root` fölé mutató `../`, ismeretlen pipe, rossz pipe-szintaxis, üres `data-sb4-toolbar` / `data-sb4-action` |
320
+ | Java-builder (`build()`) | `IllegalArgumentException`: párosítatlan / keresztezett szekció, raw tag, érvénytelen path vagy `root`, a `root` fölé mutató `../`, ismeretlen pipe, rossz pipe-szintaxis, üres `data-sb4-toolbar` / `data-sb4-action` /
321
+ `data-sb4-icon` / `data-sb4-icon-key`, `data-sb4-icon` és `data-sb4-icon-key` ugyanazon az elemen |
322
+ | Java-builder (`build()`) | `IllegalStateException`: a sablonban álló literál ikon-kulcs, amit az ikon-szótár nem tartalmaz |
304
323
  | Java-builder (`build()`), `resolveHtml` | `IllegalStateException`: feloldatlan szerver-helyőrző (`${alias:/`), lásd *A szerver-fázis* |
305
324
  | kliens | nem dob: `console.error`, és a html **feloldatlanul** kerül ki — látható hiba, nem üres kártya |
306
325
 
@@ -344,6 +363,92 @@ nézeten az oldalét). Java: `Tag.toolbar(id)` / `Tag.toolbar(id, toolbarPropert
344
363
  - A kliens csak akkor írja újra a html-t, ha a feloldás eredménye **megváltozott**; változatlan
345
364
  eredménynél a hidratált toolbarok élnek tovább.
346
365
 
366
+ ## Ikon-slot
367
+
368
+ ```html
369
+ <smart-icon data-sb4-icon="mail" data-sb4-icon-color="warn"></smart-icon>
370
+ <smart-icon data-sb4-icon="{{typeIcon}}"></smart-icon>
371
+ <smart-icon data-sb4-icon-key="{{type}}"></smart-icon>
372
+ ```
373
+
374
+ A kliens a marker-elembe egy **valódi `smart-icon`-t hidratál**, ugyanazt a komponenst, amit a
375
+ gomb, a grid-cella és a form használ. Java: `Tag.icon(name)`, `Tag.icon(name, color)`,
376
+ `Tag.icon(imageResource)`, `Tag.iconKey(key)`, a szótár a widget-builderen: `h.icon(key, resource)`
377
+ / `h.icons(map)`.
378
+
379
+ Az ikon címe négyféle lehet, és mindegyiknek megvan a maga esete:
380
+
381
+ | Forma | Mit ad az ikonnak | Mikor |
382
+ |---|---|---|
383
+ | `data-sb4-icon="mail"` | `icon` input: a név előbb a regisztrált svg-névterekben, aztán Material-ligatúraként | fix ikon, vagy adatból jövő **ikonnév** |
384
+ | `data-sb4-icon="{{typeIcon}}"`, az érték **objektum** | `imageResource` input | soronként más erőforrás (feltöltött kép, `ImageSettingApi` a modellben) |
385
+ | `data-sb4-icon="{…}"` (JSON) | `imageResource` input | a page által **egyszer** feloldott erőforrás (`Tag.icon(resource)`) |
386
+ | `data-sb4-icon-key="{{type}}"` | `imageResource` input a **szótárból** | véges típus → ikon leképezés |
387
+
388
+ - **`data-sb4-icon-color`**: a `color` input, vagyis téma- vagy host-osztálynév (`primary`,
389
+ `text-primary`, `warn` …), **nem** CSS-szín. Csak a név-formához való: az `ImageResource` a saját
390
+ `color`-ját hozza. Elhagyva a komponens alapértelmezése (`primary`) marad.
391
+ - **Objektum csak ott**, ahol az attribútum értéke pontosan egy helyőrző. Szöveggel keverve
392
+ (`data-sb4-icon="ic-{{typeIcon}}"`), pipe-pal vagy listán az érték a nyelvtan általános szabálya
393
+ szerint üres marad, warninggal. A feloldó az objektumot egy **renderelésenként újrahúzott,
394
+ véletlen tokennel** adja át a hidratálásnak, hogy adatból ne lehessen másik ikon erőforrására
395
+ hivatkozni.
396
+ - **Üres cím** (nincs ikonja ennek a sornak) → néma kihagyás, az elem üresen marad. A sablonban
397
+ **szó szerint üres** marker (`data-sb4-icon=""`) viszont `build()`-hiba, ahogy a toolbarnál.
398
+ - **Ismeretlen szótárkulcs** → nincs ikon, egy warning. A sablonban álló **literál** kulcsot, ami
399
+ nincs a szótárban, már a `build()` elutasítja.
400
+ - Egy elemen **nem állhat** a `data-sb4-icon` és a `data-sb4-icon-key` egyszerre: `build()`-hiba, a
401
+ kliens a kulcsot használja és warningot ír.
402
+ - A marker `class` és `style` attribútuma a helyén marad (a hidratálás a marker-elembe rendereli a
403
+ komponenst), a méretezés a widget CSS-éből megy: `.card-icon mat-icon { font-size: … }`.
404
+ - Az ikon a nevét a regiszterben keresi meg, ezért **egy microtaskkal később** jelenik meg — a
405
+ kliens ettől még ugyanabban a renderelésben írja ki a html-t.
406
+
407
+ ### Példa: típus szerinti ikon egy ImageResource-ból
408
+
409
+ A szerveren az `ImageSettingApi` képezi le a típust ikonra (`images-*.properties`):
410
+
411
+ ```properties
412
+ ContactType.EMAIL={"identifier":"mail","color":"accent","tooltip":{"tooltip":"E-mail cím"}}
413
+ ContactType.PHONE={"identifier":"call","color":"primary","tooltip":{"tooltip":"Telefonszám"}}
414
+ ```
415
+
416
+ **Soronként más erőforrás** — az objektum az adatban utazik:
417
+
418
+ ```java
419
+ row.getData().put("typeIcon", imageSettingApi.get("ContactType", type.name()));
420
+ ```
421
+
422
+ ```html
423
+ <div class="pg-row">
424
+ <smart-icon data-sb4-icon="{{typeIcon}}"></smart-icon>
425
+ <b>{{name}}</b>
426
+ </div>
427
+ ```
428
+
429
+ **Véges leképezés** — az erőforrás egyszer utazik, a sor csak a kódot viszi:
430
+
431
+ ```java
432
+ .htmlColumn("type", h -> h
433
+ .icon("EMAIL", imageSettingApi.get("ContactType", "EMAIL"))
434
+ .icon("PHONE", imageSettingApi.get("ContactType", "PHONE"))
435
+ .html("""
436
+ <div class="pg-row">
437
+ <smart-icon data-sb4-icon-key="{{type}}"></smart-icon>
438
+ <b>{{name}}</b>
439
+ </div>"""))
440
+ ```
441
+
442
+ Mindkettőből ugyanaz lesz a DOM-ban:
443
+
444
+ ```html
445
+ <smart-icon data-sb4-icon="…"><mat-icon class="mat-accent material-icons">mail</mat-icon></smart-icon>
446
+ ```
447
+
448
+ A szótár a **sablon** része (`HtmlProperties.icons`), nem a soré: a sima HTML widgetnél is egyszer
449
+ megy le, és egy újra-feloldás már a leküldött erőforrások közül választ. Nem tévesztendő össze a
450
+ `GridRow.icons`-szal, ami a **cella értéke mellé** rajzolt ikonokat írja le.
451
+
347
452
  ## Akció-trigger
348
453
 
349
454
  ```html
@@ -468,7 +573,7 @@ gridBuilder.htmlColumn("name", h -> h.html(t -> t
468
573
  .toolbar("ROW_TOOLBAR")))
469
574
  ```
470
575
 
471
- - **Ugyanaz a bean, mint a widgeté** (`HtmlProperties`): `html`, `root`, `fields`, `css`, és
576
+ - **Ugyanaz a bean, mint a widgeté** (`HtmlProperties`): `html`, `root`, `fields`, `css`, `icons`, és
472
577
  ugyanaz a nyelvtan, pipe-készlet, escape, stíluslap- és hibakezelés. A `root` a **sor `data`-jához** relatív; a
473
578
  `valueSets` a gridet mutató oldalé.
474
579
  - A sablon az oszlop **minden érték-renderelését** kiváltja, a `typeClass` szerinti dátum-,
@@ -518,7 +623,8 @@ gridBuilder.htmlColumn("name", h -> h.html(t -> t
518
623
  `root = "card"`, ha a sor `data`-jában a kártya a `card` oszlop alatt van. Amit a kliens-dialektus
519
624
  szándékosan nem tud, annak a helye:
520
625
 
521
- - **ikon / értékfüggő megjelenés**: szerver-származtatott mező, vagy érték-alapú CSS-osztály
626
+ - **ikon**: ikon-slot (`data-sb4-icon` / `data-sb4-icon-key`), a leképezés az `ImageSettingApi`-ban;
627
+ értékfüggő megjelenés egyébként: szerver-származtatott mező, vagy érték-alapú CSS-osztály
522
628
  (`contact-card-icon-{{channelType}}`);
523
629
  - **feltétel egy érték egyenlőségére**: sor-layout kulcs (`row.rowLayout`), vagy szerver-származtatott
524
630
  logikai mező + `{{?…}}` őr;
package/MIGRATION-7.0.md CHANGED
@@ -1874,6 +1874,15 @@ Additive, with **one behaviour change** and one removed field. The reference is
1874
1874
  going out.
1875
1875
  Inside a `{{#section}}` a placeholder reads the surrounding scope only when it says so,
1876
1876
  `{{../title}}`: one step per entering section, never above the widget's root.
1877
+ - **Icon slots (in the tree, released after 7.2.4)**: an element in the html marked with
1878
+ `data-sb4-icon` hydrates into a
1879
+ `smart-icon` — the same component a button or a cell uses. The marker addresses a name
1880
+ (`data-sb4-icon="mail"`, coloured by `data-sb4-icon-color`, a theme or host class name), an
1881
+ `ImageResource` (a placeholder that resolves to one, or the json `Tag.icon(resource)` writes
1882
+ in), or a key of the template's icon dictionary (`data-sb4-icon-key`,
1883
+ `HtmlProperties.icons`), which sends a finite type-to-icon mapping once with the template
1884
+ rather than once per row. An address that resolves to nothing renders no icon and says
1885
+ nothing; an unknown dictionary key warns. Nothing changes for a host that sends no icon slot.
1877
1886
  - **Row layouts**: a cards grid can carry its card layouts once per grid
1878
1887
  (`GridViewDescriptor.rowLayouts`, picked per row by `GridRow.rowLayout`) instead of once per
1879
1888
  row. The per-row `layoutDescriptor.GRID_ROW_LAYOUT` still works and wins over them; a
package/WIDGETS.md CHANGED
@@ -155,7 +155,9 @@ toolbar slot of the html the backend sent (`<smart-ui-action-toolbar data-sb4-to
155
155
  such a toolbar resolves by the very same pull: a `SmartActionHost` above it if one has a list, the
156
156
  screen component otherwise. That is why a row layout's html gets the row's actions on a grid card
157
157
  with no code of its own, and a table cell rendered from a column template gets them the same way.
158
- An action trigger (`data-sb4-action`) looks its action up in the same list. See `HTML-WIDGET.md`.
158
+ An action trigger (`data-sb4-action`) looks its action up in the same list, and an icon slot
159
+ (`data-sb4-icon`, `data-sb4-icon-key`) hydrates into a `smart-icon` the same way. See
160
+ `HTML-WIDGET.md`.
159
161
 
160
162
  **A `UiActionModel` is frozen.** Every field is `readonly`, and `[uiActionModels]` takes
161
163
  `readonly UiActionModel[]`. Build a new entry; never edit one. Writing into an entry a
@@ -6162,18 +6162,6 @@ const APIS$4 = [DefaultService$1];
6162
6162
  * Do not edit the class manually.
6163
6163
  */
6164
6164
 
6165
- /**
6166
- * Form layout definition
6167
- * Contains form layout definition objects.
6168
- *
6169
- * The version of the OpenAPI document: 1.0.0
6170
- * Contact: info@it4all.hu
6171
- *
6172
- * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
6173
- * https://openapi-generator.tech
6174
- * Do not edit the class manually.
6175
- */
6176
-
6177
6165
  var RecordingUploaderPropertiesOpenDirectionEnum;
6178
6166
  (function (RecordingUploaderPropertiesOpenDirectionEnum) {
6179
6167
  RecordingUploaderPropertiesOpenDirectionEnum["RIGHT"] = "RIGHT";
@@ -9653,6 +9641,79 @@ function enumAttribute(marker, name, values, ctx) {
9653
9641
  }
9654
9642
  return value;
9655
9643
  }
9644
+ /**
9645
+ * Turns every icon slot under the container into a live `smart-icon` rendered into the marker
9646
+ * element itself. A slot addresses its icon in one of three ways, and an empty address — the
9647
+ * ordinary case of a row that has no icon — is skipped without a word:
9648
+ *
9649
+ * - `data-sb4-icon` with a name, which the icon resolves against the registered svg namespaces
9650
+ * and falls back to a material ligature, optionally coloured by `data-sb4-icon-color`;
9651
+ * - `data-sb4-icon` with an image resource, either as the token the resolver left for an object
9652
+ * valued placeholder or as the JSON a backend builder wrote in;
9653
+ * - `data-sb4-icon-key` with a key of the template's icon dictionary, which travels once however
9654
+ * many rows read it.
9655
+ *
9656
+ * The refs are the caller's, like the toolbars': destroy them before the html is rewritten.
9657
+ */
9658
+ function hydrateIcons(container, ctx, icons) {
9659
+ const refs = [];
9660
+ container
9661
+ .querySelectorAll('[data-sb4-icon], [data-sb4-icon-key]')
9662
+ .forEach((marker) => {
9663
+ const key = marker.getAttribute('data-sb4-icon-key');
9664
+ const name = marker.getAttribute('data-sb4-icon');
9665
+ if (key !== null && name !== null) {
9666
+ ctx.warn('an icon slot addresses both a name and a dictionary key, the key is used');
9667
+ }
9668
+ const ref = createComponent(SmartIconComponent, {
9669
+ hostElement: marker,
9670
+ elementInjector: ctx.elementInjector,
9671
+ environmentInjector: ctx.environmentInjector,
9672
+ });
9673
+ if (key !== null) {
9674
+ const resource = icons.dictionary?.[key];
9675
+ if (key && !resource) {
9676
+ ctx.warn(`the icon dictionary has no '${key}'`);
9677
+ }
9678
+ ref.setInput('imageResource', resource);
9679
+ }
9680
+ else {
9681
+ const resource = imageResource(name, icons, ctx);
9682
+ if (resource) {
9683
+ ref.setInput('imageResource', resource);
9684
+ }
9685
+ else {
9686
+ ref.setInput('icon', name || undefined);
9687
+ ref.setInput('color', marker.getAttribute('data-sb4-icon-color') ?? undefined);
9688
+ }
9689
+ }
9690
+ // Without a zone nothing else would ever refresh a view that is in no template.
9691
+ ctx.appRef.attachView(ref.hostView);
9692
+ ref.changeDetectorRef.detectChanges();
9693
+ refs.push(ref);
9694
+ });
9695
+ return refs;
9696
+ }
9697
+ /** The resource an address stands for, undefined when it is a plain icon name. */
9698
+ function imageResource(address, icons, ctx) {
9699
+ if (icons.objects.has(address)) {
9700
+ return icons.objects.get(address);
9701
+ }
9702
+ if (address.startsWith('sb4-img:')) {
9703
+ ctx.warn(`the image resource of '${address}' is not from this render, the icon is left empty`);
9704
+ return {};
9705
+ }
9706
+ if (!address.startsWith('{')) {
9707
+ return undefined;
9708
+ }
9709
+ try {
9710
+ return JSON.parse(address);
9711
+ }
9712
+ catch {
9713
+ ctx.warn(`an icon slot holds html that is not an image resource: ${address.substring(0, 40)}`);
9714
+ return {};
9715
+ }
9716
+ }
9656
9717
 
9657
9718
  /** Pipes that must have an argument, and pipes that must not; the Java scanner checks the same. */
9658
9719
  const PIPES_REQUIRING_ARGUMENT = ['label'];
@@ -9730,6 +9791,8 @@ function formatNumber(v, digitsInfo, ctx) {
9730
9791
  }
9731
9792
 
9732
9793
  const IDENTIFIER = '[A-Za-z_][A-Za-z0-9_]*';
9794
+ /** The whole value of an icon slot's name attribute: only there may a placeholder give an object. */
9795
+ const ICON_ATTRIBUTE = /data-sb4-icon\s*=\s*"$/i;
9733
9796
  const PATH = new RegExp(`^((?:\\.\\./)*)(${IDENTIFIER}(?:\\.${IDENTIFIER})*)$`);
9734
9797
  const PIPE_NAME = new RegExp(IDENTIFIER, 'y');
9735
9798
  function resolveTemplate(template, scope, ctx) {
@@ -9738,11 +9801,33 @@ function resolveTemplate(template, scope, ctx) {
9738
9801
  nodes = parse(template);
9739
9802
  }
9740
9803
  catch (e) {
9741
- return { html: template, errors: [e.message] };
9804
+ return { html: template, errors: [e.message], objects: new Map(), identity: template };
9742
9805
  }
9743
9806
  const out = [];
9744
- render(nodes, [scope], ctx, out);
9745
- return { html: out.join(''), errors: [] };
9807
+ const sink = { prefix: `sb4-img:${nonce()}:`, objects: new Map() };
9808
+ render(nodes, [scope], ctx, out, sink);
9809
+ const html = out.join('');
9810
+ return { html, errors: [], objects: sink.objects, identity: identityOf(html, sink) };
9811
+ }
9812
+ /** The html with each token replaced by its object, so an unchanged render stays unchanged. */
9813
+ function identityOf(html, sink) {
9814
+ let identity = html;
9815
+ sink.objects.forEach((value, token) => {
9816
+ let written;
9817
+ try {
9818
+ written = JSON.stringify(value) ?? '';
9819
+ }
9820
+ catch {
9821
+ written = token.substring(sink.prefix.length);
9822
+ }
9823
+ identity = identity.split(token).join(written);
9824
+ });
9825
+ return identity;
9826
+ }
9827
+ function nonce() {
9828
+ return typeof crypto !== 'undefined' && crypto.randomUUID
9829
+ ? crypto.randomUUID()
9830
+ : String(Math.random()).substring(2);
9746
9831
  }
9747
9832
  /** Walks a dot-path; a missing, null or non-object step anywhere gives undefined. */
9748
9833
  function lookupPath(scope, path) {
@@ -9823,6 +9908,9 @@ function parse(template) {
9823
9908
  ...(path === '.' ? { parents: 0, path } : parsePath(path, tag, open)),
9824
9909
  pipes: pipe < 0 ? [] : parsePipes(content.substring(pipe + 1), tag),
9825
9910
  tag,
9911
+ resource: pipe < 0 &&
9912
+ template[end + 2] === '"' &&
9913
+ ICON_ATTRIBUTE.test(template.substring(pos, start)),
9826
9914
  });
9827
9915
  }
9828
9916
  pos = end + 2;
@@ -9909,38 +9997,38 @@ function skipSpaces(s, i) {
9909
9997
  * `scopes` holds one entry per open entering section above the root scope, the innermost last;
9910
9998
  * the parser has already checked that no `../` reaches below its bottom.
9911
9999
  */
9912
- function render(nodes, scopes, ctx, out) {
10000
+ function render(nodes, scopes, ctx, out, sink) {
9913
10001
  const scope = scopes[scopes.length - 1];
9914
10002
  for (const node of nodes) {
9915
10003
  if (node.kind === 'text') {
9916
10004
  out.push(node.text);
9917
10005
  }
9918
10006
  else if (node.kind === 'value') {
9919
- out.push(renderValue(node, scopes, ctx));
10007
+ out.push(renderValue(node, scopes, ctx, sink));
9920
10008
  }
9921
10009
  else {
9922
10010
  const value = lookup(scopes, node);
9923
10011
  if (node.sigil === '^') {
9924
10012
  if (!truthy(value)) {
9925
- render(node.children, scopes, ctx, out);
10013
+ render(node.children, scopes, ctx, out, sink);
9926
10014
  }
9927
10015
  }
9928
10016
  else if (!truthy(value)) {
9929
10017
  continue;
9930
10018
  }
9931
10019
  else if (node.sigil === '?') {
9932
- render(node.children, scopes, ctx, out);
10020
+ render(node.children, scopes, ctx, out, sink);
9933
10021
  }
9934
10022
  else if (Array.isArray(value)) {
9935
- value.forEach((item) => render(node.children, [...scopes, item], ctx, out));
10023
+ value.forEach((item) => render(node.children, [...scopes, item], ctx, out, sink));
9936
10024
  }
9937
10025
  else if (typeof value === 'object') {
9938
- render(node.children, [...scopes, value], ctx, out);
10026
+ render(node.children, [...scopes, value], ctx, out, sink);
9939
10027
  }
9940
10028
  else {
9941
10029
  ctx.warn(`{{#${node.source}}} entered a single value; use {{?${node.source}}} to guard on it`);
9942
10030
  // Still a level, so `../` inside counts the same as the server's field derivation does.
9943
- render(node.children, [...scopes, scope], ctx, out);
10031
+ render(node.children, [...scopes, scope], ctx, out, sink);
9944
10032
  }
9945
10033
  }
9946
10034
  }
@@ -9948,7 +10036,7 @@ function render(nodes, scopes, ctx, out) {
9948
10036
  function lookup(scopes, path) {
9949
10037
  return lookupPath(scopes[scopes.length - 1 - path.parents], path.path);
9950
10038
  }
9951
- function renderValue(node, scopes, ctx) {
10039
+ function renderValue(node, scopes, ctx, sink) {
9952
10040
  let value = node.path === '.' ? scopes[scopes.length - 1] : lookup(scopes, node);
9953
10041
  for (const pipe of node.pipes) {
9954
10042
  // Own names only: `constructor` and the like must not reach Object.prototype.
@@ -9963,6 +10051,12 @@ function renderValue(node, scopes, ctx) {
9963
10051
  return '';
9964
10052
  }
9965
10053
  if (typeof value === 'object') {
10054
+ // An icon slot takes an image resource as it stands; everywhere else an object is nothing.
10055
+ if (node.resource && !Array.isArray(value)) {
10056
+ const token = `${sink.prefix}${sink.objects.size}`;
10057
+ sink.objects.set(token, value);
10058
+ return token;
10059
+ }
9966
10060
  ctx.warn(`${node.tag} names an object or a list, rendered empty`);
9967
10061
  return '';
9968
10062
  }
@@ -10203,17 +10297,23 @@ function injectSmartHtmlRenderer(container) {
10203
10297
  untracked(() => triggers.mark());
10204
10298
  }, /* @ts-ignore */
10205
10299
  ...(ngDevMode ? [{ debugName: "marking" }] : /* istanbul ignore next */ []));
10206
- const destroyToolbars = () => {
10300
+ const destroyHydrated = () => {
10207
10301
  refs.forEach((ref) => ref.destroy());
10208
10302
  refs = [];
10209
10303
  };
10210
- const write = (html) => {
10211
- current = html;
10212
- // Rewriting the html does not destroy the toolbars hydrated into it.
10213
- destroyToolbars();
10304
+ const write = (resolved) => {
10305
+ current = resolved;
10306
+ // Rewriting the html does not destroy the toolbars and icons hydrated into it.
10307
+ destroyHydrated();
10214
10308
  // Trusted as authored by the backend; every value the resolver put in is escaped.
10215
- container.innerHTML = html;
10216
- refs = hydrateToolbars(container, hydration);
10309
+ container.innerHTML = resolved.html;
10310
+ refs = [
10311
+ ...hydrateToolbars(container, hydration),
10312
+ ...hydrateIcons(container, hydration, {
10313
+ dictionary: resolved.icons,
10314
+ objects: resolved.objects,
10315
+ }),
10316
+ ];
10217
10317
  triggers.mark();
10218
10318
  };
10219
10319
  const cancelLeave = () => {
@@ -10222,7 +10322,7 @@ function injectSmartHtmlRenderer(container) {
10222
10322
  container.classList.remove(LEAVING_CLASS);
10223
10323
  };
10224
10324
  /** Plays what the style sheets give the leaving class, then writes the latest pending html. */
10225
- const leaveThenWrite = (html) => {
10325
+ const leaveThenWrite = (resolved) => {
10226
10326
  // Only what the class starts counts; an entry or endless animation already running does not.
10227
10327
  const running = new Set(container.getAnimations({ subtree: true }));
10228
10328
  container.classList.add(LEAVING_CLASS);
@@ -10231,10 +10331,10 @@ function injectSmartHtmlRenderer(container) {
10231
10331
  .filter((animation) => !running.has(animation));
10232
10332
  if (!leaving.length) {
10233
10333
  container.classList.remove(LEAVING_CLASS);
10234
- write(html);
10334
+ write(resolved);
10235
10335
  return;
10236
10336
  }
10237
- pending = html;
10337
+ pending = resolved;
10238
10338
  const token = ++leave;
10239
10339
  Promise.race([
10240
10340
  Promise.allSettled(leaving.map((animation) => animation.finished)),
@@ -10253,30 +10353,32 @@ function injectSmartHtmlRenderer(container) {
10253
10353
  // the action list the triggers read.
10254
10354
  render: (newTemplate, context) => untracked(() => {
10255
10355
  template = newTemplate;
10256
- const { html, errors } = resolveTemplate(template, context.data, {
10356
+ const result = resolveTemplate(template, context.data, {
10257
10357
  valueSets: context.valueSets,
10258
10358
  locale: locale(),
10259
10359
  warn,
10260
10360
  });
10261
- errors.forEach((error) => report(`the html is left unresolved: ${error}`, console.error));
10361
+ const resolved = { ...result, icons: context.icons };
10362
+ result.errors.forEach((error) => report(`the html is left unresolved: ${error}`, console.error));
10363
+ const unchanged = resolved.identity === current?.identity;
10262
10364
  if (pending !== undefined) {
10263
10365
  // Mid-leave the latest html wins; back to the one on screen, nothing leaves after all.
10264
- if (html === current) {
10366
+ if (unchanged) {
10265
10367
  cancelLeave();
10266
10368
  }
10267
10369
  else {
10268
- pending = html;
10370
+ pending = resolved;
10269
10371
  }
10270
10372
  return;
10271
10373
  }
10272
- if (html === current) {
10374
+ if (unchanged) {
10273
10375
  return;
10274
10376
  }
10275
10377
  if (current === undefined) {
10276
- write(html);
10378
+ write(resolved);
10277
10379
  return;
10278
10380
  }
10279
- leaveThenWrite(html);
10381
+ leaveThenWrite(resolved);
10280
10382
  }),
10281
10383
  useCss: (newCss) => {
10282
10384
  const next = newCss || undefined;
@@ -10291,7 +10393,7 @@ function injectSmartHtmlRenderer(container) {
10291
10393
  disposed = true;
10292
10394
  releaseCss();
10293
10395
  marking.destroy();
10294
- destroyToolbars();
10396
+ destroyHydrated();
10295
10397
  triggers.dispose();
10296
10398
  },
10297
10399
  };
@@ -10319,6 +10421,7 @@ class SmartHtmlWidgetComponent {
10319
10421
  this.renderer.render(properties?.html ?? '', {
10320
10422
  data: this.scope(model?.data, properties?.root, properties?.html),
10321
10423
  valueSets: model?.valueSets,
10424
+ icons: properties?.icons,
10322
10425
  });
10323
10426
  });
10324
10427
  }
@@ -13111,7 +13214,7 @@ class SmartHtmlCellComponent {
13111
13214
  }
13112
13215
  ngOnChanges() {
13113
13216
  this.actionHost.actionModels.set(rowActionModels(this.row, this.gridIdentifier));
13114
- const { html, root, css } = this.template;
13217
+ const { html, root, css, icons } = this.template;
13115
13218
  const scope = rootScope(this.row.data, root);
13116
13219
  if (scope === undefined && root && this.warnedRoot !== root) {
13117
13220
  this.warnedRoot = root;
@@ -13122,6 +13225,7 @@ class SmartHtmlCellComponent {
13122
13225
  this.renderer.render(html ?? '', {
13123
13226
  data: scope,
13124
13227
  valueSets: this.smartComponent?.model?.valueSets,
13228
+ icons,
13125
13229
  });
13126
13230
  }
13127
13231
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartHtmlCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
@@ -22468,11 +22572,11 @@ class SmartFilterParamsComponent {
22468
22572
  }
22469
22573
  }
22470
22574
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFilterParamsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
22471
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartFilterParamsComponent, isStandalone: true, selector: "smart-filter-group-params", inputs: { service: { classPropertyName: "service", publicName: "service", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "filterVcRef", first: true, predicate: ["smartFilter"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "@if (service().model?.showGroups && !service().model?.readOnly) {\r\n <div\r\n class=\"targetGroupFilterParamsContainer\"\r\n >\r\n <!-- <ng-template #smartFilter></ng-template> -->\r\n <!-- <div class=\"dataSource-options\" *ngIf=\"service.model?.extarnalDatabase\">\r\n <div class=\"dataSource-btns\">\r\n <div class=\"dataSource-btn\">K\u00FCls\u0151 adatb\u00E1zis</div>\r\n <div class=\"dataSource-btn dataSource-btn-disabled\">Bels\u0151 adatb\u00E1zis</div>\r\n </div>\r\n <div class=\"dataSource-add-database\">\r\n <div class=\"dataSource-add-database-title\">\r\n <smart-icon icon=\"Ic24_View_Table\" color=\"sucess\"></smart-icon>\r\n Adatb\u00E1zis\r\n </div>\r\n <button title=\"add\" mat-icon-button (click)=\"addDataSource()\">\r\n <smart-icon icon=\"Ic24_Plus_Circle\" color=\"success\"></smart-icon>\r\n </button>\r\n </div>\r\n </div> -->\r\n <div class=\"sections\">\r\n @if (expandableSectionsFromExcel) {\r\n <smart-expandable-section\r\n class=\"excel\"\r\n [data]=\"expandableSectionsFromExcel!\"\r\n ></smart-expandable-section>\r\n }\r\n @for (data of expandableSections; track data) {\r\n <smart-expandable-section\r\n [data]=\"data\"\r\n ></smart-expandable-section>\r\n }\r\n </div>\r\n</div>\r\n}\r\n", styles: [".targetGroupFilterParamsContainer{padding:1.5rem;display:flex;flex-direction:column;gap:2.5rem;height:calc(100% - 3rem);overflow:auto;border-right:2px solid var(--primary-dark-color)}.sections{display:flex;flex-direction:column;gap:.25rem}.targetGroupFilterParamsContainer .excel::ng-deep .mat-expansion-panel-body{background-color:var(--success-lighter-color)}.targetGroupFilterParamsContainer .excel::ng-deep .mat-expansion-panel-header.mat-expanded{background-color:var(--success-color)!important}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-body{padding:0 0 0 1rem!important;background-color:var(--light-blue-color)}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header{background-color:var(--primary-light-color);padding:.75rem;height:auto}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header-title{color:var(--primary-lighter-color);font-weight:600}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header.mat-expanded{background-color:var(--accent-color)!important;padding:.75rem;height:auto}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header.mat-expanded .mat-expansion-panel-header-title{color:#fff}.dataSource-btns{display:flex;flex-direction:row;justify-content:space-around;background-color:var(--success-lighter-color);height:44px;padding:8px 12px;align-items:center;border:1.5px solid var(--success-color);border-left:6px solid var(--success-color);border-radius:4px}.dataSource-btn{display:flex;padding:1rem 2rem;justify-content:center;align-items:center;background-color:var(--success-color);border-radius:4px;cursor:pointer}.dataSource-btn-disabled{background-color:var(--success-lighter-color);cursor:auto}.dataSource-add-database{display:flex;flex-direction:row;justify-content:space-between;background-color:var(--success-lighter-color);padding:8px;border-radius:4px}.dataSource-add-database-title{display:flex;flex-direction:row;gap:1rem;align-items:center}.dataSource-options{display:flex;flex-direction:column;gap:1rem}\n"], dependencies: [{ kind: "component", type: ExpandableSectionComponent, selector: "smart-expandable-section", inputs: ["data", "index"] }] }); }
22575
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: SmartFilterParamsComponent, isStandalone: true, selector: "smart-filter-group-params", inputs: { service: { classPropertyName: "service", publicName: "service", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "filterVcRef", first: true, predicate: ["smartFilter"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "@if (service().model?.showGroups && !service().model?.readOnly) {\r\n <div\r\n class=\"targetGroupFilterParamsContainer\"\r\n >\r\n <!-- <ng-template #smartFilter></ng-template> -->\r\n <!-- <div class=\"dataSource-options\" *ngIf=\"service.model?.extarnalDatabase\">\r\n <div class=\"dataSource-btns\">\r\n <div class=\"dataSource-btn\">K\u00FCls\u0151 adatb\u00E1zis</div>\r\n <div class=\"dataSource-btn dataSource-btn-disabled\">Bels\u0151 adatb\u00E1zis</div>\r\n </div>\r\n <div class=\"dataSource-add-database\">\r\n <div class=\"dataSource-add-database-title\">\r\n <smart-icon icon=\"Ic24_View_Table\" color=\"sucess\"></smart-icon>\r\n Adatb\u00E1zis\r\n </div>\r\n <button title=\"add\" mat-icon-button (click)=\"addDataSource()\">\r\n <smart-icon icon=\"Ic24_Plus_Circle\" color=\"success\"></smart-icon>\r\n </button>\r\n </div>\r\n </div> -->\r\n <div class=\"sections\">\r\n @if (expandableSectionsFromExcel) {\r\n <smart-expandable-section\r\n class=\"excel\"\r\n [data]=\"expandableSectionsFromExcel!\"\r\n ></smart-expandable-section>\r\n }\r\n @for (data of expandableSections; track data) {\r\n <smart-expandable-section\r\n [data]=\"data\"\r\n ></smart-expandable-section>\r\n }\r\n </div>\r\n</div>\r\n}\r\n", styles: [".targetGroupFilterParamsContainer{padding:1.5rem;display:flex;flex-direction:column;gap:2.5rem;height:calc(100% - 3rem);overflow:auto;border-right:2px solid var(--primary-dark-color)}.sections{display:flex;flex-direction:column;gap:.25rem}.targetGroupFilterParamsContainer .excel::ng-deep .mat-expansion-panel-body{background-color:var(--success-lighter-color)}.targetGroupFilterParamsContainer .excel::ng-deep .mat-expansion-panel-header.mat-expanded{background-color:var(--success-color)!important}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-body{padding:0 0 0 1rem!important;background-color:var(--light-blue-color)}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header{background-color:var(--primary-light-color);padding:.75rem;height:auto}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header-title{color:var(--primary-lighter-color);font-weight:600}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header.mat-expanded{background-color:var(--sb4-expansion-panel-expanded-bg-color, var(--accent-color))!important;padding:.75rem;height:auto}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header.mat-expanded .mat-expansion-panel-header-title{color:var(--sb4-expansion-panel-expanded-title-color, #fff)!important}.dataSource-btns{display:flex;flex-direction:row;justify-content:space-around;background-color:var(--success-lighter-color);height:44px;padding:8px 12px;align-items:center;border:1.5px solid var(--success-color);border-left:6px solid var(--success-color);border-radius:4px}.dataSource-btn{display:flex;padding:1rem 2rem;justify-content:center;align-items:center;background-color:var(--success-color);border-radius:4px;cursor:pointer}.dataSource-btn-disabled{background-color:var(--success-lighter-color);cursor:auto}.dataSource-add-database{display:flex;flex-direction:row;justify-content:space-between;background-color:var(--success-lighter-color);padding:8px;border-radius:4px}.dataSource-add-database-title{display:flex;flex-direction:row;gap:1rem;align-items:center}.dataSource-options{display:flex;flex-direction:column;gap:1rem}\n"], dependencies: [{ kind: "component", type: ExpandableSectionComponent, selector: "smart-expandable-section", inputs: ["data", "index"] }] }); }
22472
22576
  }
22473
22577
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartFilterParamsComponent, decorators: [{
22474
22578
  type: Component,
22475
- args: [{ selector: 'smart-filter-group-params', imports: [ExpandableSectionComponent], template: "@if (service().model?.showGroups && !service().model?.readOnly) {\r\n <div\r\n class=\"targetGroupFilterParamsContainer\"\r\n >\r\n <!-- <ng-template #smartFilter></ng-template> -->\r\n <!-- <div class=\"dataSource-options\" *ngIf=\"service.model?.extarnalDatabase\">\r\n <div class=\"dataSource-btns\">\r\n <div class=\"dataSource-btn\">K\u00FCls\u0151 adatb\u00E1zis</div>\r\n <div class=\"dataSource-btn dataSource-btn-disabled\">Bels\u0151 adatb\u00E1zis</div>\r\n </div>\r\n <div class=\"dataSource-add-database\">\r\n <div class=\"dataSource-add-database-title\">\r\n <smart-icon icon=\"Ic24_View_Table\" color=\"sucess\"></smart-icon>\r\n Adatb\u00E1zis\r\n </div>\r\n <button title=\"add\" mat-icon-button (click)=\"addDataSource()\">\r\n <smart-icon icon=\"Ic24_Plus_Circle\" color=\"success\"></smart-icon>\r\n </button>\r\n </div>\r\n </div> -->\r\n <div class=\"sections\">\r\n @if (expandableSectionsFromExcel) {\r\n <smart-expandable-section\r\n class=\"excel\"\r\n [data]=\"expandableSectionsFromExcel!\"\r\n ></smart-expandable-section>\r\n }\r\n @for (data of expandableSections; track data) {\r\n <smart-expandable-section\r\n [data]=\"data\"\r\n ></smart-expandable-section>\r\n }\r\n </div>\r\n</div>\r\n}\r\n", styles: [".targetGroupFilterParamsContainer{padding:1.5rem;display:flex;flex-direction:column;gap:2.5rem;height:calc(100% - 3rem);overflow:auto;border-right:2px solid var(--primary-dark-color)}.sections{display:flex;flex-direction:column;gap:.25rem}.targetGroupFilterParamsContainer .excel::ng-deep .mat-expansion-panel-body{background-color:var(--success-lighter-color)}.targetGroupFilterParamsContainer .excel::ng-deep .mat-expansion-panel-header.mat-expanded{background-color:var(--success-color)!important}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-body{padding:0 0 0 1rem!important;background-color:var(--light-blue-color)}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header{background-color:var(--primary-light-color);padding:.75rem;height:auto}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header-title{color:var(--primary-lighter-color);font-weight:600}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header.mat-expanded{background-color:var(--accent-color)!important;padding:.75rem;height:auto}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header.mat-expanded .mat-expansion-panel-header-title{color:#fff}.dataSource-btns{display:flex;flex-direction:row;justify-content:space-around;background-color:var(--success-lighter-color);height:44px;padding:8px 12px;align-items:center;border:1.5px solid var(--success-color);border-left:6px solid var(--success-color);border-radius:4px}.dataSource-btn{display:flex;padding:1rem 2rem;justify-content:center;align-items:center;background-color:var(--success-color);border-radius:4px;cursor:pointer}.dataSource-btn-disabled{background-color:var(--success-lighter-color);cursor:auto}.dataSource-add-database{display:flex;flex-direction:row;justify-content:space-between;background-color:var(--success-lighter-color);padding:8px;border-radius:4px}.dataSource-add-database-title{display:flex;flex-direction:row;gap:1rem;align-items:center}.dataSource-options{display:flex;flex-direction:column;gap:1rem}\n"] }]
22579
+ args: [{ selector: 'smart-filter-group-params', imports: [ExpandableSectionComponent], template: "@if (service().model?.showGroups && !service().model?.readOnly) {\r\n <div\r\n class=\"targetGroupFilterParamsContainer\"\r\n >\r\n <!-- <ng-template #smartFilter></ng-template> -->\r\n <!-- <div class=\"dataSource-options\" *ngIf=\"service.model?.extarnalDatabase\">\r\n <div class=\"dataSource-btns\">\r\n <div class=\"dataSource-btn\">K\u00FCls\u0151 adatb\u00E1zis</div>\r\n <div class=\"dataSource-btn dataSource-btn-disabled\">Bels\u0151 adatb\u00E1zis</div>\r\n </div>\r\n <div class=\"dataSource-add-database\">\r\n <div class=\"dataSource-add-database-title\">\r\n <smart-icon icon=\"Ic24_View_Table\" color=\"sucess\"></smart-icon>\r\n Adatb\u00E1zis\r\n </div>\r\n <button title=\"add\" mat-icon-button (click)=\"addDataSource()\">\r\n <smart-icon icon=\"Ic24_Plus_Circle\" color=\"success\"></smart-icon>\r\n </button>\r\n </div>\r\n </div> -->\r\n <div class=\"sections\">\r\n @if (expandableSectionsFromExcel) {\r\n <smart-expandable-section\r\n class=\"excel\"\r\n [data]=\"expandableSectionsFromExcel!\"\r\n ></smart-expandable-section>\r\n }\r\n @for (data of expandableSections; track data) {\r\n <smart-expandable-section\r\n [data]=\"data\"\r\n ></smart-expandable-section>\r\n }\r\n </div>\r\n</div>\r\n}\r\n", styles: [".targetGroupFilterParamsContainer{padding:1.5rem;display:flex;flex-direction:column;gap:2.5rem;height:calc(100% - 3rem);overflow:auto;border-right:2px solid var(--primary-dark-color)}.sections{display:flex;flex-direction:column;gap:.25rem}.targetGroupFilterParamsContainer .excel::ng-deep .mat-expansion-panel-body{background-color:var(--success-lighter-color)}.targetGroupFilterParamsContainer .excel::ng-deep .mat-expansion-panel-header.mat-expanded{background-color:var(--success-color)!important}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-body{padding:0 0 0 1rem!important;background-color:var(--light-blue-color)}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header{background-color:var(--primary-light-color);padding:.75rem;height:auto}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header-title{color:var(--primary-lighter-color);font-weight:600}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header.mat-expanded{background-color:var(--sb4-expansion-panel-expanded-bg-color, var(--accent-color))!important;padding:.75rem;height:auto}.targetGroupFilterParamsContainer::ng-deep .mat-expansion-panel-header.mat-expanded .mat-expansion-panel-header-title{color:var(--sb4-expansion-panel-expanded-title-color, #fff)!important}.dataSource-btns{display:flex;flex-direction:row;justify-content:space-around;background-color:var(--success-lighter-color);height:44px;padding:8px 12px;align-items:center;border:1.5px solid var(--success-color);border-left:6px solid var(--success-color);border-radius:4px}.dataSource-btn{display:flex;padding:1rem 2rem;justify-content:center;align-items:center;background-color:var(--success-color);border-radius:4px;cursor:pointer}.dataSource-btn-disabled{background-color:var(--success-lighter-color);cursor:auto}.dataSource-add-database{display:flex;flex-direction:row;justify-content:space-between;background-color:var(--success-lighter-color);padding:8px;border-radius:4px}.dataSource-add-database-title{display:flex;flex-direction:row;gap:1rem;align-items:center}.dataSource-options{display:flex;flex-direction:column;gap:1rem}\n"] }]
22476
22580
  }], ctorParameters: () => [], propDecorators: { service: [{ type: i0.Input, args: [{ isSignal: true, alias: "service", required: true }] }], filterVcRef: [{
22477
22581
  type: ViewChild,
22478
22582
  args: ['smartFilter', { read: ViewContainerRef }]