@smartbit4all/ng-client 7.2.3 → 7.2.5
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 +110 -4
- package/MIGRATION-7.0.md +23 -0
- package/WIDGETS.md +3 -1
- package/fesm2022/smartbit4all-ng-client.mjs +184 -43
- package/fesm2022/smartbit4all-ng-client.mjs.map +1 -1
- package/package.json +1 -1
- package/smartbit4all-ng-client-7.2.5.tgz +0 -0
- package/types/smartbit4all-ng-client.d.ts +685 -677
- package/smartbit4all-ng-client-7.2.3.tgz +0 -0
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
|
|
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
|
@@ -720,6 +720,13 @@ went 17 → 22 with the library still installed:
|
|
|
720
720
|
is not blocking. It becomes a *hard* install failure the moment `@angular/build`
|
|
721
721
|
is a direct devDependency rather than a nested one, which is what happened in the
|
|
722
722
|
library workspace. `karma@~6.4.4` clears it.
|
|
723
|
+
- **`ngx-markdown` 22 lazily imports `marked-katex-extension`**, an optional peer. The
|
|
724
|
+
application builder leaves that import alone; the webpack `browser` builder every measured
|
|
725
|
+
host still runs resolves it, and the build fails with `Can't resolve
|
|
726
|
+
'marked-katex-extension'` against ngx-markdown's own bundle. Declare
|
|
727
|
+
`katex@^0.16` and `marked-katex-extension@^5`: webpack only puts them in a lazy chunk that
|
|
728
|
+
loads when KaTeX is switched on. ngx-markdown 22 also wants `marked@^17 || ^18` declared
|
|
729
|
+
(app-formmate, 2026-09-22).
|
|
723
730
|
|
|
724
731
|
The general shape: **`ng update` needs `--force` at every hop** (the library and
|
|
725
732
|
PrimeNG both peer-cap at Angular 17 until the very end), and `--force` covers the
|
|
@@ -1327,6 +1334,13 @@ provideSmartNgClient({
|
|
|
1327
1334
|
|
|
1328
1335
|
`SmartViewContextService.getSmartViewContextApiErrorByCode()` is no longer `async`.
|
|
1329
1336
|
|
|
1337
|
+
**`SmartComponent` no longer has a `translate` member**, and a host that injected
|
|
1338
|
+
ngx-translate's `TranslateService` under that name as `override translate: TranslateService`
|
|
1339
|
+
now gets `TS4113` on every such constructor (app-formmate had 22). Do not just delete
|
|
1340
|
+
`override`: it was also what made the parameter a property, so without an access modifier
|
|
1341
|
+
`this.translate` disappears. Write `public translate: TranslateService` — and keep `override`
|
|
1342
|
+
on subclasses of your own pages, which now override *your* base class instead of the library's.
|
|
1343
|
+
|
|
1330
1344
|
#### If your host still bootstraps with zone.js
|
|
1331
1345
|
|
|
1332
1346
|
Nothing forces you to drop it — the library works either way, because it no longer depends
|
|
@@ -1860,6 +1874,15 @@ Additive, with **one behaviour change** and one removed field. The reference is
|
|
|
1860
1874
|
going out.
|
|
1861
1875
|
Inside a `{{#section}}` a placeholder reads the surrounding scope only when it says so,
|
|
1862
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.
|
|
1863
1886
|
- **Row layouts**: a cards grid can carry its card layouts once per grid
|
|
1864
1887
|
(`GridViewDescriptor.rowLayouts`, picked per row by `GridRow.rowLayout`) instead of once per
|
|
1865
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
|
|
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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, reflectComponentType, Injectable, inject, DOCUMENT, PLATFORM_ID, Optional, Inject, RendererStyleFlags2, input, computed, Component, HostListener, HostBinding, Directive, ViewContainerRef, ElementRef, Renderer2, ChangeDetectorRef, Input, output, signal, Injector, ViewChild, ViewChildren, effect, afterNextRender, NgZone, forwardRef, createComponent, CSP_NONCE, EnvironmentInjector, ApplicationRef, untracked, DestroyRef, ChangeDetectionStrategy, ViewEncapsulation, Pipe,
|
|
2
|
+
import { InjectionToken, reflectComponentType, Injectable, inject, DOCUMENT, PLATFORM_ID, Optional, Inject, RendererStyleFlags2, input, computed, Component, HostListener, HostBinding, Directive, ViewContainerRef, ElementRef, Renderer2, ChangeDetectorRef, Input, output, signal, Injector, SkipSelf, NgModule, ViewChild, ViewChildren, effect, afterNextRender, NgZone, forwardRef, createComponent, CSP_NONCE, EnvironmentInjector, ApplicationRef, untracked, DestroyRef, ChangeDetectionStrategy, ViewEncapsulation, Pipe, EventEmitter, Output, viewChild, makeEnvironmentProviders, importProvidersFrom } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common/http';
|
|
4
4
|
import { HttpHeaders, HttpContext, HttpErrorResponse, HttpParams, provideHttpClient, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
|
|
5
5
|
import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
|
|
@@ -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";
|
|
@@ -7634,6 +7622,42 @@ var ToolbarDirection;
|
|
|
7634
7622
|
ToolbarDirection["VERTICAL"] = "VERTICAL";
|
|
7635
7623
|
})(ToolbarDirection || (ToolbarDirection = {}));
|
|
7636
7624
|
|
|
7625
|
+
let ApiModule$1 = class ApiModule {
|
|
7626
|
+
static forRoot(configurationFactory) {
|
|
7627
|
+
return {
|
|
7628
|
+
ngModule: ApiModule,
|
|
7629
|
+
providers: [{ provide: Configuration$5, useFactory: configurationFactory }]
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
constructor(parentModule, http) {
|
|
7633
|
+
if (parentModule) {
|
|
7634
|
+
throw new Error('ApiModule is already loaded. Import in your base AppModule only.');
|
|
7635
|
+
}
|
|
7636
|
+
if (!http) {
|
|
7637
|
+
throw new Error('You need to import the HttpClientModule in your AppModule! \n' +
|
|
7638
|
+
'See also https://github.com/angular/angular/issues/20575');
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ApiModule, deps: [{ token: ApiModule, optional: true, skipSelf: true }, { token: i1.HttpClient, optional: true }], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
7642
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: ApiModule }); }
|
|
7643
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ApiModule }); }
|
|
7644
|
+
};
|
|
7645
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ApiModule$1, decorators: [{
|
|
7646
|
+
type: NgModule,
|
|
7647
|
+
args: [{
|
|
7648
|
+
imports: [],
|
|
7649
|
+
declarations: [],
|
|
7650
|
+
exports: [],
|
|
7651
|
+
providers: []
|
|
7652
|
+
}]
|
|
7653
|
+
}], ctorParameters: () => [{ type: ApiModule$1, decorators: [{
|
|
7654
|
+
type: Optional
|
|
7655
|
+
}, {
|
|
7656
|
+
type: SkipSelf
|
|
7657
|
+
}] }, { type: i1.HttpClient, decorators: [{
|
|
7658
|
+
type: Optional
|
|
7659
|
+
}] }] });
|
|
7660
|
+
|
|
7637
7661
|
class UiMenuService {
|
|
7638
7662
|
constructor() {
|
|
7639
7663
|
this.closeMenus$ = new Subject();
|
|
@@ -9617,6 +9641,79 @@ function enumAttribute(marker, name, values, ctx) {
|
|
|
9617
9641
|
}
|
|
9618
9642
|
return value;
|
|
9619
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
|
+
}
|
|
9620
9717
|
|
|
9621
9718
|
/** Pipes that must have an argument, and pipes that must not; the Java scanner checks the same. */
|
|
9622
9719
|
const PIPES_REQUIRING_ARGUMENT = ['label'];
|
|
@@ -9694,6 +9791,8 @@ function formatNumber(v, digitsInfo, ctx) {
|
|
|
9694
9791
|
}
|
|
9695
9792
|
|
|
9696
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;
|
|
9697
9796
|
const PATH = new RegExp(`^((?:\\.\\./)*)(${IDENTIFIER}(?:\\.${IDENTIFIER})*)$`);
|
|
9698
9797
|
const PIPE_NAME = new RegExp(IDENTIFIER, 'y');
|
|
9699
9798
|
function resolveTemplate(template, scope, ctx) {
|
|
@@ -9702,11 +9801,33 @@ function resolveTemplate(template, scope, ctx) {
|
|
|
9702
9801
|
nodes = parse(template);
|
|
9703
9802
|
}
|
|
9704
9803
|
catch (e) {
|
|
9705
|
-
return { html: template, errors: [e.message] };
|
|
9804
|
+
return { html: template, errors: [e.message], objects: new Map(), identity: template };
|
|
9706
9805
|
}
|
|
9707
9806
|
const out = [];
|
|
9708
|
-
|
|
9709
|
-
|
|
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);
|
|
9710
9831
|
}
|
|
9711
9832
|
/** Walks a dot-path; a missing, null or non-object step anywhere gives undefined. */
|
|
9712
9833
|
function lookupPath(scope, path) {
|
|
@@ -9787,6 +9908,9 @@ function parse(template) {
|
|
|
9787
9908
|
...(path === '.' ? { parents: 0, path } : parsePath(path, tag, open)),
|
|
9788
9909
|
pipes: pipe < 0 ? [] : parsePipes(content.substring(pipe + 1), tag),
|
|
9789
9910
|
tag,
|
|
9911
|
+
resource: pipe < 0 &&
|
|
9912
|
+
template[end + 2] === '"' &&
|
|
9913
|
+
ICON_ATTRIBUTE.test(template.substring(pos, start)),
|
|
9790
9914
|
});
|
|
9791
9915
|
}
|
|
9792
9916
|
pos = end + 2;
|
|
@@ -9873,38 +9997,38 @@ function skipSpaces(s, i) {
|
|
|
9873
9997
|
* `scopes` holds one entry per open entering section above the root scope, the innermost last;
|
|
9874
9998
|
* the parser has already checked that no `../` reaches below its bottom.
|
|
9875
9999
|
*/
|
|
9876
|
-
function render(nodes, scopes, ctx, out) {
|
|
10000
|
+
function render(nodes, scopes, ctx, out, sink) {
|
|
9877
10001
|
const scope = scopes[scopes.length - 1];
|
|
9878
10002
|
for (const node of nodes) {
|
|
9879
10003
|
if (node.kind === 'text') {
|
|
9880
10004
|
out.push(node.text);
|
|
9881
10005
|
}
|
|
9882
10006
|
else if (node.kind === 'value') {
|
|
9883
|
-
out.push(renderValue(node, scopes, ctx));
|
|
10007
|
+
out.push(renderValue(node, scopes, ctx, sink));
|
|
9884
10008
|
}
|
|
9885
10009
|
else {
|
|
9886
10010
|
const value = lookup(scopes, node);
|
|
9887
10011
|
if (node.sigil === '^') {
|
|
9888
10012
|
if (!truthy(value)) {
|
|
9889
|
-
render(node.children, scopes, ctx, out);
|
|
10013
|
+
render(node.children, scopes, ctx, out, sink);
|
|
9890
10014
|
}
|
|
9891
10015
|
}
|
|
9892
10016
|
else if (!truthy(value)) {
|
|
9893
10017
|
continue;
|
|
9894
10018
|
}
|
|
9895
10019
|
else if (node.sigil === '?') {
|
|
9896
|
-
render(node.children, scopes, ctx, out);
|
|
10020
|
+
render(node.children, scopes, ctx, out, sink);
|
|
9897
10021
|
}
|
|
9898
10022
|
else if (Array.isArray(value)) {
|
|
9899
|
-
value.forEach((item) => render(node.children, [...scopes, item], ctx, out));
|
|
10023
|
+
value.forEach((item) => render(node.children, [...scopes, item], ctx, out, sink));
|
|
9900
10024
|
}
|
|
9901
10025
|
else if (typeof value === 'object') {
|
|
9902
|
-
render(node.children, [...scopes, value], ctx, out);
|
|
10026
|
+
render(node.children, [...scopes, value], ctx, out, sink);
|
|
9903
10027
|
}
|
|
9904
10028
|
else {
|
|
9905
10029
|
ctx.warn(`{{#${node.source}}} entered a single value; use {{?${node.source}}} to guard on it`);
|
|
9906
10030
|
// Still a level, so `../` inside counts the same as the server's field derivation does.
|
|
9907
|
-
render(node.children, [...scopes, scope], ctx, out);
|
|
10031
|
+
render(node.children, [...scopes, scope], ctx, out, sink);
|
|
9908
10032
|
}
|
|
9909
10033
|
}
|
|
9910
10034
|
}
|
|
@@ -9912,7 +10036,7 @@ function render(nodes, scopes, ctx, out) {
|
|
|
9912
10036
|
function lookup(scopes, path) {
|
|
9913
10037
|
return lookupPath(scopes[scopes.length - 1 - path.parents], path.path);
|
|
9914
10038
|
}
|
|
9915
|
-
function renderValue(node, scopes, ctx) {
|
|
10039
|
+
function renderValue(node, scopes, ctx, sink) {
|
|
9916
10040
|
let value = node.path === '.' ? scopes[scopes.length - 1] : lookup(scopes, node);
|
|
9917
10041
|
for (const pipe of node.pipes) {
|
|
9918
10042
|
// Own names only: `constructor` and the like must not reach Object.prototype.
|
|
@@ -9927,6 +10051,12 @@ function renderValue(node, scopes, ctx) {
|
|
|
9927
10051
|
return '';
|
|
9928
10052
|
}
|
|
9929
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
|
+
}
|
|
9930
10060
|
ctx.warn(`${node.tag} names an object or a list, rendered empty`);
|
|
9931
10061
|
return '';
|
|
9932
10062
|
}
|
|
@@ -10167,17 +10297,23 @@ function injectSmartHtmlRenderer(container) {
|
|
|
10167
10297
|
untracked(() => triggers.mark());
|
|
10168
10298
|
}, /* @ts-ignore */
|
|
10169
10299
|
...(ngDevMode ? [{ debugName: "marking" }] : /* istanbul ignore next */ []));
|
|
10170
|
-
const
|
|
10300
|
+
const destroyHydrated = () => {
|
|
10171
10301
|
refs.forEach((ref) => ref.destroy());
|
|
10172
10302
|
refs = [];
|
|
10173
10303
|
};
|
|
10174
|
-
const write = (
|
|
10175
|
-
current =
|
|
10176
|
-
// Rewriting the html does not destroy the toolbars hydrated into it.
|
|
10177
|
-
|
|
10304
|
+
const write = (resolved) => {
|
|
10305
|
+
current = resolved;
|
|
10306
|
+
// Rewriting the html does not destroy the toolbars and icons hydrated into it.
|
|
10307
|
+
destroyHydrated();
|
|
10178
10308
|
// Trusted as authored by the backend; every value the resolver put in is escaped.
|
|
10179
|
-
container.innerHTML = html;
|
|
10180
|
-
refs =
|
|
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
|
+
];
|
|
10181
10317
|
triggers.mark();
|
|
10182
10318
|
};
|
|
10183
10319
|
const cancelLeave = () => {
|
|
@@ -10186,7 +10322,7 @@ function injectSmartHtmlRenderer(container) {
|
|
|
10186
10322
|
container.classList.remove(LEAVING_CLASS);
|
|
10187
10323
|
};
|
|
10188
10324
|
/** Plays what the style sheets give the leaving class, then writes the latest pending html. */
|
|
10189
|
-
const leaveThenWrite = (
|
|
10325
|
+
const leaveThenWrite = (resolved) => {
|
|
10190
10326
|
// Only what the class starts counts; an entry or endless animation already running does not.
|
|
10191
10327
|
const running = new Set(container.getAnimations({ subtree: true }));
|
|
10192
10328
|
container.classList.add(LEAVING_CLASS);
|
|
@@ -10195,10 +10331,10 @@ function injectSmartHtmlRenderer(container) {
|
|
|
10195
10331
|
.filter((animation) => !running.has(animation));
|
|
10196
10332
|
if (!leaving.length) {
|
|
10197
10333
|
container.classList.remove(LEAVING_CLASS);
|
|
10198
|
-
write(
|
|
10334
|
+
write(resolved);
|
|
10199
10335
|
return;
|
|
10200
10336
|
}
|
|
10201
|
-
pending =
|
|
10337
|
+
pending = resolved;
|
|
10202
10338
|
const token = ++leave;
|
|
10203
10339
|
Promise.race([
|
|
10204
10340
|
Promise.allSettled(leaving.map((animation) => animation.finished)),
|
|
@@ -10217,30 +10353,32 @@ function injectSmartHtmlRenderer(container) {
|
|
|
10217
10353
|
// the action list the triggers read.
|
|
10218
10354
|
render: (newTemplate, context) => untracked(() => {
|
|
10219
10355
|
template = newTemplate;
|
|
10220
|
-
const
|
|
10356
|
+
const result = resolveTemplate(template, context.data, {
|
|
10221
10357
|
valueSets: context.valueSets,
|
|
10222
10358
|
locale: locale(),
|
|
10223
10359
|
warn,
|
|
10224
10360
|
});
|
|
10225
|
-
|
|
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;
|
|
10226
10364
|
if (pending !== undefined) {
|
|
10227
10365
|
// Mid-leave the latest html wins; back to the one on screen, nothing leaves after all.
|
|
10228
|
-
if (
|
|
10366
|
+
if (unchanged) {
|
|
10229
10367
|
cancelLeave();
|
|
10230
10368
|
}
|
|
10231
10369
|
else {
|
|
10232
|
-
pending =
|
|
10370
|
+
pending = resolved;
|
|
10233
10371
|
}
|
|
10234
10372
|
return;
|
|
10235
10373
|
}
|
|
10236
|
-
if (
|
|
10374
|
+
if (unchanged) {
|
|
10237
10375
|
return;
|
|
10238
10376
|
}
|
|
10239
10377
|
if (current === undefined) {
|
|
10240
|
-
write(
|
|
10378
|
+
write(resolved);
|
|
10241
10379
|
return;
|
|
10242
10380
|
}
|
|
10243
|
-
leaveThenWrite(
|
|
10381
|
+
leaveThenWrite(resolved);
|
|
10244
10382
|
}),
|
|
10245
10383
|
useCss: (newCss) => {
|
|
10246
10384
|
const next = newCss || undefined;
|
|
@@ -10255,7 +10393,7 @@ function injectSmartHtmlRenderer(container) {
|
|
|
10255
10393
|
disposed = true;
|
|
10256
10394
|
releaseCss();
|
|
10257
10395
|
marking.destroy();
|
|
10258
|
-
|
|
10396
|
+
destroyHydrated();
|
|
10259
10397
|
triggers.dispose();
|
|
10260
10398
|
},
|
|
10261
10399
|
};
|
|
@@ -10283,6 +10421,7 @@ class SmartHtmlWidgetComponent {
|
|
|
10283
10421
|
this.renderer.render(properties?.html ?? '', {
|
|
10284
10422
|
data: this.scope(model?.data, properties?.root, properties?.html),
|
|
10285
10423
|
valueSets: model?.valueSets,
|
|
10424
|
+
icons: properties?.icons,
|
|
10286
10425
|
});
|
|
10287
10426
|
});
|
|
10288
10427
|
}
|
|
@@ -13075,7 +13214,7 @@ class SmartHtmlCellComponent {
|
|
|
13075
13214
|
}
|
|
13076
13215
|
ngOnChanges() {
|
|
13077
13216
|
this.actionHost.actionModels.set(rowActionModels(this.row, this.gridIdentifier));
|
|
13078
|
-
const { html, root, css } = this.template;
|
|
13217
|
+
const { html, root, css, icons } = this.template;
|
|
13079
13218
|
const scope = rootScope(this.row.data, root);
|
|
13080
13219
|
if (scope === undefined && root && this.warnedRoot !== root) {
|
|
13081
13220
|
this.warnedRoot = root;
|
|
@@ -13086,6 +13225,7 @@ class SmartHtmlCellComponent {
|
|
|
13086
13225
|
this.renderer.render(html ?? '', {
|
|
13087
13226
|
data: scope,
|
|
13088
13227
|
valueSets: this.smartComponent?.model?.valueSets,
|
|
13228
|
+
icons,
|
|
13089
13229
|
});
|
|
13090
13230
|
}
|
|
13091
13231
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SmartHtmlCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
@@ -23624,6 +23764,7 @@ class SmartComponentLayoutComponent {
|
|
|
23624
23764
|
['smartComponentLayout', data],
|
|
23625
23765
|
['parentLayoutComponent', this.parentLayoutComponent],
|
|
23626
23766
|
]),
|
|
23767
|
+
isExpanded: data.expanded ?? false,
|
|
23627
23768
|
headerToolbarId: this.smartComponentLayout?.expandableSectionHeaderToolbarId,
|
|
23628
23769
|
customComponent: SmartComponentLayoutComponent,
|
|
23629
23770
|
};
|