@yuuvis/client-framework 3.7.1 → 3.8.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuuvis/client-framework",
3
- "version": "3.7.1",
3
+ "version": "3.8.1",
4
4
  "author": "OPTIMAL SYSTEMS GmbH <npm@optimal-systems.de>",
5
5
  "license": "MIT",
6
6
  "peerDependencies": {
@@ -8,16 +8,16 @@
8
8
  "@angular/common": "^21.2.9",
9
9
  "@angular/core": "^21.2.9",
10
10
  "angular-gridster2": "^21.0.1",
11
- "@yuuvis/client-core": "^3.7.1",
12
- "@yuuvis/client-shell-core": "^3.7.1",
13
- "@yuuvis/client-components": "^3.7.1",
11
+ "@yuuvis/client-core": "^3.8.1",
12
+ "@yuuvis/client-shell-core": "^3.8.1",
13
+ "@yuuvis/client-components": "^3.8.1",
14
14
  "ng-dynamic-component": "^10.8.2",
15
15
  "modern-normalize": "^3.0.1"
16
16
  },
17
17
  "dependencies": {
18
18
  "@angular/material": "^21.2.7",
19
19
  "@ngrx/signals": "^21.1.0",
20
- "@yuuvis/material": "^3.7.1",
20
+ "@yuuvis/material": "^3.8.1",
21
21
  "@yuuvis/media-viewer": "^3.0.4",
22
22
  "angular-split": "^20.0.0",
23
23
  "vis-network": "^10.0.2",
@@ -149,6 +149,10 @@
149
149
  "types": "./types/yuuvis-client-framework-simple-search.d.ts",
150
150
  "default": "./fesm2022/yuuvis-client-framework-simple-search.mjs"
151
151
  },
152
+ "./smart-search": {
153
+ "types": "./types/yuuvis-client-framework-smart-search.d.ts",
154
+ "default": "./fesm2022/yuuvis-client-framework-smart-search.mjs"
155
+ },
152
156
  "./sort": {
153
157
  "types": "./types/yuuvis-client-framework-sort.d.ts",
154
158
  "default": "./fesm2022/yuuvis-client-framework-sort.mjs"
@@ -0,0 +1,109 @@
1
+ # @yuuvis/client-framework/smart-search
2
+
3
+ Secondary entry point of `@yuuvis/client-framework`. Import from `@yuuvis/client-framework/smart-search`.
4
+
5
+ A visual, guided query builder that turns chip-based user input into a [CMIS](https://docs.oasis-open.org/cmis/CMIS/v1.1/CMIS-v1.1.html) query. Instead of typing query syntax, users pick object types, fields, operators and values through an inline autocomplete; the component assembles a valid, injection-safe `SELECT … WHERE …` statement and emits it on every change.
6
+
7
+ ## Core features
8
+
9
+ - **Full-text search bar** — a `CONTAINS` search with a selectable scope (`all` = metadata + content, `metadata`, or `content`) and an optional object-type restriction.
10
+ - **Type blocks** — one or more blocks, each targeting one or more object types. Conditions inside a block apply to the fields shared by all of its types (plus inherited base/system fields such as `system:creationDate`).
11
+ - **Guided condition building** — a step-by-step `type → field → operator → value` flow. Operators are filtered to those that make sense for the field's type (e.g. `>`/`<` for numbers and dates, date presets for dates, `= true/false` for booleans, `empty`/`not empty` for everything).
12
+ - **Real metadata widgets** — the value step renders the field's actual editor (datepicker, catalog select, organization picker, …) via the metadata-form renderer, so values are entered the same way as in object forms.
13
+ - **Nested groups** — parenthesized AND/OR sub-expressions, nestable to arbitrary depth.
14
+ - **Table conditions** — query into table-type properties by their columns, matched against any row (`tableField[*].(…)`).
15
+ - **Save / restore** — capture the whole search as a plain-data, JSON-serializable [`SmartSearchState`](src/lib/smart-search.interface.ts) and reload it later.
16
+ - **Injection-safe output** — all values are escaped; values that don't match their field's declared type are quoted as a fallback so a hostile value can never break out of the literal.
17
+
18
+ ## Usage
19
+
20
+ `SmartSearchComponent` is a standalone component — import it directly.
21
+
22
+ ```ts
23
+ import { Component, signal } from '@angular/core';
24
+ import { SmartSearchComponent } from '@yuuvis/client-framework/smart-search';
25
+
26
+ @Component({
27
+ selector: 'app-search',
28
+ imports: [SmartSearchComponent],
29
+ template: `
30
+ <yuv-smart-search
31
+ [types]="['document', 'invoice']"
32
+ [skipProperties]="['system:traceId']"
33
+ (queryChange)="onQuery($event)"
34
+ />
35
+ `
36
+ })
37
+ export class SearchComponent {
38
+ query = signal('');
39
+
40
+ onQuery(cmisQuery: string) {
41
+ // An empty string means "no query" — treat it as a cleared search.
42
+ this.query.set(cmisQuery);
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### Inputs
48
+
49
+ | Input | Type | Default | Description |
50
+ | ---------------- | ---------- | ------- | --------------------------------------------------------------------------------------------- |
51
+ | `types` | `string[]` | `[]` | Object-type ids that may be searched. Set at least one to enable building blocks. |
52
+ | `skipProperties` | `string[]` | `[]` | Field ids to hide from the field picker (e.g. internal/system properties). |
53
+
54
+ ### Outputs
55
+
56
+ | Output | Type | Description |
57
+ | ------------- | -------- | ------------------------------------------------------------------------------------------- |
58
+ | `queryChange` | `string` | The current CMIS query. Emitted on every change; `''` means an empty search ("no query"). |
59
+
60
+ ### Saving and restoring state
61
+
62
+ `getState()` returns a serializable snapshot; `loadState()` restores one. `clear()` resets the whole search.
63
+
64
+ ```ts
65
+ import { Component, viewChild } from '@angular/core';
66
+ import { SmartSearchComponent } from '@yuuvis/client-framework/smart-search';
67
+
68
+ @Component({ /* … */ })
69
+ export class SearchComponent {
70
+ private search = viewChild.required(SmartSearchComponent);
71
+
72
+ persist() {
73
+ localStorage.setItem('search', JSON.stringify(this.search().getState()));
74
+ }
75
+
76
+ restore() {
77
+ const raw = localStorage.getItem('search');
78
+ if (raw) this.search().loadState(JSON.parse(raw));
79
+ }
80
+
81
+ reset() {
82
+ this.search().clear();
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## How it produces a query
88
+
89
+ The emitted query joins independent **units** — the full-text unit and each type block — with `OR`:
90
+
91
+ ```sql
92
+ -- types=['invoice'], full-text "acme" in metadata, condition: total > 1000
93
+ SELECT * FROM system:object
94
+ WHERE (system:metadata CONTAINS('acme'))
95
+ OR (objectTypeId = 'invoice' AND total > 1000)
96
+ ```
97
+
98
+ Within a block, the type restriction is always `AND`-ed with the condition expression, and the conditions combine with the block's own AND/OR combinator. Nested groups add parentheses; table conditions render as `tableField[*].(col op val …)`.
99
+
100
+ ## Architecture
101
+
102
+ | File | Responsibility |
103
+ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
104
+ | [`smart-search.component.ts`](src/lib/smart-search.component.ts) | Host component — public API, focus management, autocomplete plumbing. |
105
+ | [`smart-search-edit.controller.ts`](src/lib/smart-search-edit.controller.ts) | All state and mutators (signals); provided per component instance. |
106
+ | [`smart-search-group.component.ts`](src/lib/smart-search-group.component.ts) | Recursive renderer for a block body, nested group or table. |
107
+ | [`smart-search.query.ts`](src/lib/smart-search.query.ts) | Pure CMIS query builders (`buildCmisQuery`, value escaping, operator rendering). |
108
+ | [`smart-search.tree.ts`](src/lib/smart-search.tree.ts) | Immutable tree helpers (find / update / remove containers and conditions). |
109
+ | [`smart-search.interface.ts`](src/lib/smart-search.interface.ts) | Data model (`SearchBlock`, `FieldCondition`, `ConditionGroup`, `SmartSearchState`, …). |
@@ -25,6 +25,10 @@ declare class MetadataFormElementRegistry {
25
25
  private _edit;
26
26
  private _search;
27
27
  private _create;
28
+ private _rawDefaults;
29
+ private _rawEdit;
30
+ private _rawSearch;
31
+ private _rawCreate;
28
32
  NAME_PROPERTY_PREFIX: string;
29
33
  /**
30
34
  * Get a template to render a certain form element
@@ -59,6 +63,39 @@ declare class MetadataFormElementRegistry {
59
63
  * @param propertyType The internal type to remove the component for
60
64
  */
61
65
  _removeDefaultElementTemplate(propertyType: string): void;
66
+ /**
67
+ * Get a raw (unwrapped) form element template — only the inner widget,
68
+ * without the surrounding `mat-form-field` / label / hint / error shell.
69
+ * Use this when composing the widget into a custom layout (e.g. smart-search).
70
+ * @param propertyType The internal type to get the template for
71
+ * @param situation Form situation
72
+ * @returns TemplateRef or undefined
73
+ */
74
+ getRawElementTemplate(propertyType: string, situation?: string): TemplateRef<any> | undefined;
75
+ /**
76
+ * Register a raw form element template for a certain internal type.
77
+ * @param propertyType The internal type to provide the template for
78
+ * @param templateRef The template
79
+ * @param situation Form situation to provide the form element for (defaults to EDIT)
80
+ */
81
+ addRawElementTemplate(propertyType: string, templateRef: TemplateRef<any>, situation?: Situation): void;
82
+ /**
83
+ * Remove a registered raw form element template.
84
+ * @param propertyType The internal type to remove the template for
85
+ * @param situation Form situation
86
+ */
87
+ removeRawElementTemplate(propertyType: string, situation?: Situation): void;
88
+ /**
89
+ * Register a default raw form element template (used across all situations).
90
+ * @param propertyType The internal type to provide the template for
91
+ * @param templateRef The default template
92
+ */
93
+ _addDefaultRawElementTemplate(propertyType: string, templateRef: TemplateRef<any>): void;
94
+ /**
95
+ * Remove a registered default raw form element template.
96
+ * @param propertyType The internal type to remove the template for
97
+ */
98
+ _removeDefaultRawElementTemplate(propertyType: string): void;
62
99
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MetadataFormElementRegistry, never>;
63
100
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<MetadataFormElementRegistry>;
64
101
  }
@@ -165,6 +202,49 @@ declare class ObjectMetadataElementTemplateDirective implements OnInit, OnDestro
165
202
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ObjectMetadataElementTemplateDirective, "[yuvMetadataElementTemplate]", never, { "yuvMetadataElementTemplate": { "alias": "yuvMetadataElementTemplate"; "required": false; "isSignal": true; }; "situation": { "alias": "situation"; "required": false; "isSignal": true; }; "propertyType": { "alias": "propertyType"; "required": false; "isSignal": true; }; "propertyName": { "alias": "propertyName"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
166
203
  }
167
204
 
205
+ /**
206
+ * Directive to be applied to an `ng-template` that renders only the raw form
207
+ * widget for a metadata property (without the surrounding `mat-form-field`
208
+ * label/hint/error shell). The template is registered in the raw bucket of
209
+ * `MetadataFormElementRegistry`, parallel to the wrapped templates registered
210
+ * via `yuvMetadataElementTemplate`.
211
+ *
212
+ * Use this to expose a type-aware input that can be composed into custom
213
+ * layouts — for example smart-search, where conditions are built inline as
214
+ * a pill chain and a full form-field shell would be visually wrong.
215
+ *
216
+ * @example
217
+ * <ng-template #rawString yuvMetadataElementRaw="default" propertyType="string" let-ctx>
218
+ * <yuv-string [formControl]="ctx.ctrl" [required]="ctx.field.required" />
219
+ * </ng-template>
220
+ */
221
+ declare class ObjectMetadataElementRawDirective implements OnInit, OnDestroy {
222
+ #private;
223
+ /**
224
+ * Bucket to register the template. Use 'default' for templates that should be
225
+ * used across all situations.
226
+ */
227
+ yuvMetadataElementRaw: _angular_core.InputSignal<string | undefined>;
228
+ /**
229
+ * Situation to register the template for. Defaults to `EDIT`.
230
+ */
231
+ situation: _angular_core.InputSignal<Situation>;
232
+ /**
233
+ * Internal property type to register the template under. You need to set
234
+ * either `propertyType` or `propertyName`. When both are set, `propertyName`
235
+ * wins because it is more precise.
236
+ */
237
+ propertyType: _angular_core.InputSignal<string | undefined>;
238
+ /**
239
+ * Register the raw template by metadata field name.
240
+ */
241
+ propertyName: _angular_core.InputSignal<string | undefined>;
242
+ ngOnInit(): void;
243
+ ngOnDestroy(): void;
244
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ObjectMetadataElementRawDirective, never>;
245
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ObjectMetadataElementRawDirective, "[yuvMetadataElementRaw]", never, { "yuvMetadataElementRaw": { "alias": "yuvMetadataElementRaw"; "required": false; "isSignal": true; }; "situation": { "alias": "situation"; "required": false; "isSignal": true; }; "propertyType": { "alias": "propertyType"; "required": false; "isSignal": true; }; "propertyName": { "alias": "propertyName"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
246
+ }
247
+
168
248
  /**
169
249
  * Component to render a metadata form field within an object form. These forms are
170
250
  * created to render and edit metadata of DMS objects. This component is used as a wrapper
@@ -175,6 +255,12 @@ declare class MetadataFormFieldComponent {
175
255
  readonly: boolean;
176
256
  formChangedSubject: _angular_core.InputSignal<Subject<ParentFormChangedEvent> | undefined>;
177
257
  formField: _angular_core.InputSignal<ObjectTypeField>;
258
+ /**
259
+ * Which template variant to render. `default` renders the wrapped form field
260
+ * (mat-form-field with label, hint, error). `raw` renders only the inner
261
+ * widget — useful for composing into custom layouts such as smart-search.
262
+ */
263
+ variant: _angular_core.InputSignal<"default" | "raw">;
178
264
  elementTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
179
265
  context: _angular_core.Signal<MetadataFormFieldContext>;
180
266
  /**
@@ -182,8 +268,8 @@ declare class MetadataFormFieldComponent {
182
268
  */
183
269
  situation: _angular_core.InputSignal<string | undefined>;
184
270
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MetadataFormFieldComponent, never>;
185
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MetadataFormFieldComponent, "yuv-metadata-form-field", never, { "formChangedSubject": { "alias": "formChangedSubject"; "required": false; "isSignal": true; }; "formField": { "alias": "field"; "required": true; "isSignal": true; }; "situation": { "alias": "situation"; "required": false; "isSignal": true; }; }, {}, never, never, true, [{ directive: typeof i1.NoopValueAccessorDirective; inputs: {}; outputs: {}; }]>;
271
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MetadataFormFieldComponent, "yuv-metadata-form-field", never, { "formChangedSubject": { "alias": "formChangedSubject"; "required": false; "isSignal": true; }; "formField": { "alias": "field"; "required": true; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "situation": { "alias": "situation"; "required": false; "isSignal": true; }; }, {}, never, never, true, [{ directive: typeof i1.NoopValueAccessorDirective; inputs: {}; outputs: {}; }]>;
186
272
  }
187
273
 
188
- export { MetadataFormElementRegistry, MetadataFormFieldComponent, ObjectMetadataElementErrorDirective, ObjectMetadataElementLabelDirective, ObjectMetadataElementTemplateDirective };
274
+ export { MetadataFormElementRegistry, MetadataFormFieldComponent, ObjectMetadataElementErrorDirective, ObjectMetadataElementLabelDirective, ObjectMetadataElementRawDirective, ObjectMetadataElementTemplateDirective };
189
275
  export type { MetadataFormFieldContext, ParentFormChangedEvent };