@acorex/cdk 22.1.0-next.2 → 22.1.0-next.20

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.
@@ -1,11 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { model, output, Component, inject, ElementRef, signal, afterNextRender, Injectable, ChangeDetectorRef, ViewContainerRef, EventEmitter, DOCUMENT, PLATFORM_ID, Renderer2, Input, Directive, NgModule, InjectionToken, Output, input, effect } from '@angular/core';
2
+ import { model, output, Component, inject, ElementRef, signal, afterNextRender, Injectable, EventEmitter, PLATFORM_ID, ChangeDetectorRef, ViewContainerRef, DOCUMENT as DOCUMENT$1, Renderer2, Input, Directive, NgModule, InjectionToken, Output, input, effect } from '@angular/core';
3
3
  import { AXHtmlUtil } from '@acorex/core/utils';
4
- import { flatten, cloneDeep, isEqual, omit, get } from 'lodash-es';
5
4
  import { isBrowser } from '@acorex/core/platform';
6
5
  import { AXValidationService } from '@acorex/core/validation';
6
+ import { DOCUMENT, isPlatformBrowser } from '@angular/common';
7
+ import { omit, isEqual, flatten, cloneDeep, get } from 'lodash-es';
7
8
  import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
8
- import { isPlatformBrowser } from '@angular/common';
9
9
  import { noop, Subscription, BehaviorSubject, debounceTime, distinctUntilChanged, Subject, fromEvent, skip } from 'rxjs';
10
10
  import { DataSource } from '@angular/cdk/collections';
11
11
  import { EventManager } from '@angular/platform-browser';
@@ -66,6 +66,13 @@ class NXComponent {
66
66
  get nativeElement() {
67
67
  return this.#elementRef.nativeElement;
68
68
  }
69
+ /**
70
+ * Gets the host HTML element of the component.
71
+ * Same contract as classic editors so `ax-label` / `ax-form-field` can associate the field.
72
+ */
73
+ getHostElement() {
74
+ return this.nativeElement;
75
+ }
69
76
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NXComponent, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
70
77
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NXComponent }); }
71
78
  }
@@ -136,25 +143,155 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
136
143
  }]
137
144
  }], propDecorators: { look: [{ type: i0.Input, args: [{ isSignal: true, alias: "look", required: false }] }, { type: i0.Output, args: ["lookChange"] }] } });
138
145
 
139
- class NXValueComponent {
146
+ /**
147
+ * Signal-based value editor with the same `ax-validation-rule` / `ax-form` contract as the classic value mixins.
148
+ */
149
+ class NXValueComponent extends NXComponent {
140
150
  constructor() {
141
- this.readonly = model(/* @ts-ignore */
142
- ...(ngDevMode ? [undefined, { debugName: "readonly" }] : /* istanbul ignore next */ []));
143
- this.name = model(/* @ts-ignore */
144
- ...(ngDevMode ? [undefined, { debugName: "name" }] : /* istanbul ignore next */ []));
145
- this.value = model(/* @ts-ignore */
146
- ...(ngDevMode ? [undefined, { debugName: "value" }] : /* istanbul ignore next */ []));
147
- this.onValueChanged = output();
148
- }
149
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NXValueComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
150
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.8", type: NXValueComponent, isStandalone: true, selector: "ng-component", inputs: { readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { readonly: "readonlyChange", name: "nameChange", value: "valueChange", onValueChanged: "onValueChanged" }, ngImport: i0, template: '', isInline: true }); }
151
+ super(...arguments);
152
+ this.validationRulesChange = new EventEmitter();
153
+ this.#validationRules = [];
154
+ this.#state = 'clear';
155
+ this.#validationService = inject(AXValidationService);
156
+ this.#document = inject(DOCUMENT);
157
+ this.#platformId = inject(PLATFORM_ID);
158
+ this.#registerValidation = afterNextRender(() => {
159
+ this.registerValidation();
160
+ });
161
+ }
162
+ #validationRules;
163
+ #state;
164
+ #validationService;
165
+ #document;
166
+ #platformId;
167
+ #registerValidation;
168
+ get isRequired() {
169
+ return this.#validationRules.some((rule) => rule.rule == 'required');
170
+ }
171
+ get state() {
172
+ return this.#state;
173
+ }
174
+ registerValidation() {
175
+ this.nativeElement.querySelectorAll('[ax-form-item="true"]').forEach((node) => {
176
+ node.removeAttribute('ax-form-item');
177
+ });
178
+ this.nativeElement.setAttribute('ax-form-item', 'true');
179
+ }
180
+ addValidationRule(rule) {
181
+ this.#validationRules.push(rule);
182
+ this.validationRulesChange.emit(this.#validationRules);
183
+ }
184
+ removeValidationRule(ruleToRemove) {
185
+ this.#validationRules = this.#validationRules.filter((rule) => {
186
+ if (rule.rule !== ruleToRemove.rule) {
187
+ return true;
188
+ }
189
+ const ruleOptions = omit(rule.options, ['title']);
190
+ const removeOptions = omit(ruleToRemove.options, ['title']);
191
+ return !isEqual(ruleOptions['pattern'], removeOptions['pattern']);
192
+ });
193
+ this.validationRulesChange.emit(this.#validationRules);
194
+ this.setState('clear');
195
+ }
196
+ removeValidationRuleByType(type) {
197
+ this.#validationRules = this.#validationRules.filter((rule) => rule.rule !== type);
198
+ this.validationRulesChange.emit(this.#validationRules);
199
+ this.setState('clear');
200
+ }
201
+ async validate() {
202
+ const containerClasses = ['ax-editor-container', 'ax-checkbox', 'ax-radio'];
203
+ const container = containerClasses.some((className) => this.nativeElement.classList.contains(className))
204
+ ? this.nativeElement
205
+ : this.nativeElement.querySelector('.ax-editor-container');
206
+ const formField = (container || this.nativeElement).closest('ax-form-field') || container?.closest('ax-checkbox');
207
+ const label = formField?.querySelector('ax-label') || formField?.querySelector('label');
208
+ const rules = this.#validationService.ruleFor(this.#currentValue());
209
+ this.#validationRules.forEach((rule) => {
210
+ rule.options ??= {};
211
+ rule.options['title'] = (label?.firstChild?.textContent ?? this.#currentName())?.replace('*', '').trim();
212
+ rules.addRule(rule.rule, rule.options);
213
+ });
214
+ const result = await rules.validate();
215
+ if (result.result) {
216
+ this.setState('clear');
217
+ }
218
+ else {
219
+ const message = result.rules.find((rule) => !rule.result)?.message;
220
+ this.setState('error', message);
221
+ }
222
+ return result;
223
+ }
224
+ setState(state, ...args) {
225
+ if (!isBrowser()) {
226
+ return;
227
+ }
228
+ const container = this.nativeElement.classList.contains('ax-editor-container')
229
+ ? this.nativeElement
230
+ : this.nativeElement.querySelector('.ax-editor-container');
231
+ const formField = (container || this.nativeElement).closest('ax-form-field');
232
+ const label = formField?.querySelector('ax-label') || formField?.querySelector('label');
233
+ let target = container || this.nativeElement.firstElementChild;
234
+ if (target?.querySelector('.ax-error-container')) {
235
+ target = target.querySelector('.ax-error-container');
236
+ }
237
+ const parent = target?.parentElement;
238
+ if (!target || !parent) {
239
+ return;
240
+ }
241
+ switch (state) {
242
+ case 'clear':
243
+ this.#state = 'clear';
244
+ parent.querySelector('span.ax-error-message')?.remove();
245
+ label?.classList.remove('ax-state-error');
246
+ target.classList.remove('ax-state-error', 'ax-state-success');
247
+ break;
248
+ case 'success':
249
+ this.setState('clear');
250
+ target.classList.add('ax-state-success');
251
+ this.#state = 'success';
252
+ break;
253
+ case 'error':
254
+ this.setState('clear');
255
+ if (isPlatformBrowser(this.#platformId)) {
256
+ const message = args[0] ?? '';
257
+ if (message) {
258
+ const span = this.#document.createElement('span');
259
+ span.innerHTML = message;
260
+ span.classList.add('ax-error-message');
261
+ parent.appendChild(span);
262
+ }
263
+ }
264
+ label?.classList.add('ax-state-error');
265
+ target.classList.add('ax-state-error');
266
+ this.#state = 'error';
267
+ break;
268
+ }
269
+ }
270
+ resetErrors() {
271
+ this.setState('clear');
272
+ }
273
+ commitValue(value, _isUserInteraction = false) {
274
+ const model = this.#valueModel();
275
+ model?.set?.(value);
276
+ }
277
+ #currentValue() {
278
+ return this.#resolve(this.value);
279
+ }
280
+ #currentName() {
281
+ return String(this.#resolve(this.name) ?? '');
282
+ }
283
+ #valueModel() {
284
+ return this.value;
285
+ }
286
+ #resolve(prop) {
287
+ return typeof prop === 'function' ? prop() : prop;
288
+ }
289
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NXValueComponent, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
290
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NXValueComponent }); }
151
291
  }
152
292
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NXValueComponent, decorators: [{
153
- type: Component,
154
- args: [{
155
- template: '',
156
- }]
157
- }], propDecorators: { readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }, { type: i0.Output, args: ["readonlyChange"] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }, { type: i0.Output, args: ["nameChange"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onValueChanged: [{ type: i0.Output, args: ["onValueChanged"] }] } });
293
+ type: Injectable
294
+ }] });
158
295
 
159
296
  const defaultOffset = 3;
160
297
  function convertToPlacement(value) {
@@ -575,7 +712,7 @@ class MXValueComponent extends MXInteractiveComponent {
575
712
  this._isUserInteraction = false;
576
713
  this.stateChange = new EventEmitter();
577
714
  this._state = 'clear';
578
- this.document = inject(DOCUMENT);
715
+ this.document = inject(DOCUMENT$1);
579
716
  this.platformID = inject(PLATFORM_ID);
580
717
  this.validationService = inject(AXValidationService);
581
718
  this.validationRulesChange = new EventEmitter();
@@ -1047,6 +1184,10 @@ class AXListDataSource extends DataSource {
1047
1184
  .pipe(distinctUntilChanged())
1048
1185
  .subscribe((range) => {
1049
1186
  this._range = range;
1187
+ if (range.end <= range.start) {
1188
+ this.source.setPage(0);
1189
+ return;
1190
+ }
1050
1191
  const startPage = this.getPageForIndex(range.start);
1051
1192
  const endPage = this.getPageForIndex(range.end - 1);
1052
1193
  for (let i = startPage; i <= endPage; i++) {
@@ -1094,6 +1235,7 @@ class AXDataSource {
1094
1235
  this._totalCount = 0;
1095
1236
  this._items = [];
1096
1237
  this.fetchedPages = new Set();
1238
+ this.inflightPages = new Set();
1097
1239
  this._page = 0;
1098
1240
  this._query = { skip: 0, take: 0, sort: [] };
1099
1241
  this.onChanged = new Subject();
@@ -1106,18 +1248,26 @@ class AXDataSource {
1106
1248
  this._items = new Array(0);
1107
1249
  this._query.take = this.config.pageSize;
1108
1250
  }
1251
+ pageHasCachedItems(page) {
1252
+ const start = page * this.config.pageSize;
1253
+ return this._cachedItems.slice(start, start + this.config.pageSize).some((item) => item != null);
1254
+ }
1109
1255
  load() {
1110
1256
  const page = this._page;
1111
- if (this.fetchedPages.has(page) && this.useCache) {
1257
+ if (this.inflightPages.has(page)) {
1258
+ return;
1259
+ }
1260
+ if (this.fetchedPages.has(page) && this.useCache && this.pageHasCachedItems(page)) {
1112
1261
  return;
1113
1262
  }
1114
1263
  this.setLoadingState(true);
1115
- this.fetchedPages.add(page);
1264
+ this.inflightPages.add(page);
1116
1265
  this._query.take = this.config.pageSize;
1117
1266
  this._query.skip = page * this.config.pageSize;
1118
1267
  this.config
1119
1268
  .load({ ...this._query })
1120
1269
  .then((result) => {
1270
+ this.fetchedPages.add(page);
1121
1271
  if (this._totalCount != result.total) {
1122
1272
  this._cachedItems = new Array(result.total);
1123
1273
  this._totalCount = result.total;
@@ -1132,8 +1282,12 @@ class AXDataSource {
1132
1282
  totalPages: this.totalPages,
1133
1283
  };
1134
1284
  this.onChanged.next(event);
1285
+ })
1286
+ .catch(() => {
1287
+ this.fetchedPages.delete(page);
1135
1288
  })
1136
1289
  .finally(() => {
1290
+ this.inflightPages.delete(page);
1137
1291
  this.setLoadingState(false);
1138
1292
  });
1139
1293
  }
@@ -1170,6 +1324,7 @@ class AXDataSource {
1170
1324
  this._items = new Array(0);
1171
1325
  this._cachedItems = new Array(this.config.pageSize);
1172
1326
  this.fetchedPages.clear();
1327
+ this.inflightPages.clear();
1173
1328
  this._page = 0;
1174
1329
  this._totalCount = 0;
1175
1330
  }
@@ -1263,7 +1418,7 @@ class AXAutoFocusDirective {
1263
1418
  this._host = inject(AXFocusableComponent, { host: true, self: true, optional: true });
1264
1419
  this.autoFocus = true;
1265
1420
  this.time = 50;
1266
- this.document = inject(DOCUMENT);
1421
+ this.document = inject(DOCUMENT$1);
1267
1422
  this.platformID = inject(PLATFORM_ID);
1268
1423
  }
1269
1424
  ngAfterViewInit() {
@@ -1309,7 +1464,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1309
1464
  class AXHotkeysService {
1310
1465
  constructor() {
1311
1466
  this.eventManager = inject(EventManager);
1312
- this.document = inject(DOCUMENT);
1467
+ this.document = inject(DOCUMENT$1);
1313
1468
  this.defaults = {
1314
1469
  element: this.document,
1315
1470
  };
@@ -2491,7 +2646,7 @@ class AXRippleDirective {
2491
2646
  this.cdr = inject(ChangeDetectorRef);
2492
2647
  this.enabled = true;
2493
2648
  this.axRippleColor = 'var(--ax-comp-ripple-color, rgba(255, 255, 255, 0.3))';
2494
- this.document = inject(DOCUMENT);
2649
+ this.document = inject(DOCUMENT$1);
2495
2650
  this.platformID = inject(PLATFORM_ID);
2496
2651
  }
2497
2652
  ngOnInit() {