valtech-components 2.0.1012 → 2.0.1013

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.
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
54
54
  * Current version of valtech-components.
55
55
  * This is automatically updated during the publish process.
56
56
  */
57
- const VERSION = '2.0.1012';
57
+ const VERSION = '2.0.1013';
58
58
 
59
59
  // Control de estado de refresco (singleton a nivel de módulo)
60
60
  let isRefreshing = false;
@@ -49404,9 +49404,76 @@ class ApiKeysModalComponent {
49404
49404
  this.errorMsg = signal('');
49405
49405
  this.secret = signal(null);
49406
49406
  this.copied = signal(false);
49407
- this.name = '';
49408
- this.expiresInDays = null;
49409
- this.selectedPerms = signal(new Set());
49407
+ /**
49408
+ * FormMetadata del form de crear key (val-form). Reactivo a idioma + permisos
49409
+ * disponibles + estado submitting. Reemplaza los ion-input/ion-checkbox crudos
49410
+ * por val-form (consistencia con el resto de modales del factory).
49411
+ */
49412
+ this.createForm = computed(() => {
49413
+ this.i18n.lang();
49414
+ const working = this.submitting();
49415
+ const nameField = {
49416
+ token: 'apikey-name',
49417
+ name: 'name',
49418
+ label: this.t('nameLabel'),
49419
+ hint: '',
49420
+ placeholder: this.t('namePlaceholder'),
49421
+ type: InputType.TEXT,
49422
+ order: 1,
49423
+ validators: [Validators.required, Validators.maxLength(120)],
49424
+ errors: { required: this.t('noName') },
49425
+ value: '',
49426
+ state: ComponentStates.ENABLED,
49427
+ };
49428
+ const permsField = {
49429
+ token: 'apikey-perms',
49430
+ name: 'permissions',
49431
+ label: this.t('permissionsLabel'),
49432
+ hint: '',
49433
+ placeholder: '',
49434
+ type: InputType.MULTI_SELECT,
49435
+ order: 2,
49436
+ validators: [Validators.required],
49437
+ errors: { required: this.t('noPerms') },
49438
+ value: '',
49439
+ options: this.availablePermissions().map((p, i) => ({
49440
+ id: p,
49441
+ name: p,
49442
+ order: i + 1,
49443
+ })),
49444
+ state: ComponentStates.ENABLED,
49445
+ };
49446
+ const expiresField = {
49447
+ token: 'apikey-expires',
49448
+ name: 'expiresInDays',
49449
+ label: this.t('expiresLabel'),
49450
+ hint: '',
49451
+ placeholder: '',
49452
+ type: InputType.NUMBER,
49453
+ order: 3,
49454
+ validators: [Validators.min(1)],
49455
+ errors: {},
49456
+ value: '',
49457
+ state: ComponentStates.ENABLED,
49458
+ };
49459
+ const submitButton = {
49460
+ token: 'apikey-create-submit',
49461
+ text: this.t('create'),
49462
+ color: 'dark',
49463
+ type: 'submit',
49464
+ fill: 'solid',
49465
+ size: 'default',
49466
+ shape: 'round',
49467
+ expand: 'block',
49468
+ state: working ? ComponentStates.WORKING : ComponentStates.ENABLED,
49469
+ };
49470
+ return {
49471
+ name: '',
49472
+ state: working ? ComponentStates.WORKING : ComponentStates.ENABLED,
49473
+ sections: [{ name: '', order: 1, fields: [nameField, permsField, expiresField] }],
49474
+ actions: submitButton,
49475
+ };
49476
+ });
49410
49477
  if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
49411
49478
  this.i18n.registerContent(DEFAULT_NAMESPACE$5, API_KEYS_MODAL_I18N);
49412
49479
  }
@@ -49433,39 +49500,36 @@ class ApiKeysModalComponent {
49433
49500
  }
49434
49501
  openCreate() {
49435
49502
  this.errorMsg.set('');
49436
- this.name = '';
49437
- this.expiresInDays = null;
49438
- this.selectedPerms.set(new Set());
49439
49503
  this.showCreate.set(true);
49440
49504
  }
49441
- togglePerm(perm, ev) {
49442
- const checked = ev.detail?.checked ?? false;
49443
- const next = new Set(this.selectedPerms());
49444
- if (checked) {
49445
- next.add(perm);
49446
- }
49447
- else {
49448
- next.delete(perm);
49449
- }
49450
- this.selectedPerms.set(next);
49451
- }
49452
- submit() {
49505
+ onCreateSubmit(event) {
49506
+ if (this.submitting())
49507
+ return;
49453
49508
  this.errorMsg.set('');
49454
- if (!this.name.trim()) {
49509
+ const name = String(event.fields['name'] ?? '').trim();
49510
+ // MULTI_SELECT puede devolver array o csv según el render — normalizamos.
49511
+ const rawPerms = event.fields['permissions'];
49512
+ const permissions = Array.isArray(rawPerms)
49513
+ ? rawPerms
49514
+ : String(rawPerms ?? '')
49515
+ .split(',')
49516
+ .map(s => s.trim())
49517
+ .filter(Boolean);
49518
+ const expiresRaw = Number(event.fields['expiresInDays']);
49519
+ if (!name) {
49455
49520
  this.errorMsg.set(this.t('noName'));
49456
49521
  return;
49457
49522
  }
49458
- const permissions = Array.from(this.selectedPerms());
49459
- if (permissions.length === 0) {
49523
+ if (!permissions.length) {
49460
49524
  this.errorMsg.set(this.t('noPerms'));
49461
49525
  return;
49462
49526
  }
49463
49527
  this.submitting.set(true);
49464
49528
  this.apiKeys
49465
49529
  .create({
49466
- name: this.name.trim(),
49530
+ name,
49467
49531
  permissions,
49468
- expiresInDays: this.expiresInDays && this.expiresInDays > 0 ? this.expiresInDays : undefined,
49532
+ expiresInDays: Number.isFinite(expiresRaw) && expiresRaw > 0 ? expiresRaw : undefined,
49469
49533
  })
49470
49534
  .subscribe({
49471
49535
  next: res => {
@@ -49570,45 +49634,13 @@ class ApiKeysModalComponent {
49570
49634
  <p class="apikey-error">{{ errorMsg() }}</p>
49571
49635
  }
49572
49636
 
49573
- <!-- Crear -->
49637
+ <!-- Crear — val-form (consistencia con el resto de modales del factory). -->
49574
49638
  @if (showCreate()) {
49575
49639
  <div class="apikey-create">
49576
- <ion-input
49577
- label="{{ t('nameLabel') }}"
49578
- labelPlacement="stacked"
49579
- [placeholder]="t('namePlaceholder')"
49580
- [(ngModel)]="name"
49581
- />
49582
- <p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
49583
- <div class="apikey-perms">
49584
- @for (p of availablePermissions(); track p) {
49585
- <ion-checkbox
49586
- labelPlacement="end"
49587
- [checked]="selectedPerms().has(p)"
49588
- (ionChange)="togglePerm(p, $event)"
49589
- >
49590
- {{ p }}
49591
- </ion-checkbox>
49592
- }
49593
- </div>
49594
- <ion-input
49595
- label="{{ t('expiresLabel') }}"
49596
- labelPlacement="stacked"
49597
- type="number"
49598
- [(ngModel)]="expiresInDays"
49599
- />
49600
- <div class="apikey-actions">
49601
- <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49602
- @if (submitting()) {
49603
- <ion-spinner name="dots" />&nbsp;{{ t('creating') }}
49604
- } @else {
49605
- {{ t('create') }}
49606
- }
49607
- </ion-button>
49608
- <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49609
- {{ t('cancel') }}
49610
- </ion-button>
49611
- </div>
49640
+ <val-form [props]="createForm()" (onSubmit)="onCreateSubmit($event)" />
49641
+ <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49642
+ {{ t('cancel') }}
49643
+ </ion-button>
49612
49644
  </div>
49613
49645
  } @else {
49614
49646
  <div class="apikey-actions">
@@ -49620,20 +49652,18 @@ class ApiKeysModalComponent {
49620
49652
  }
49621
49653
  }
49622
49654
  </ion-content>
49623
- `, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.apikey-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.apikey-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:10px;border:1.5px solid var(--ion-color-light)}.apikey-row__main{display:flex;flex-direction:column}.apikey-row__name{font-weight:600;color:var(--ion-color-dark)}.apikey-row__meta{font-size:12px;color:var(--ion-color-medium-shade, #6b6e76)}.apikey-create{margin-top:8px;padding:16px;border-radius:12px;background:var(--ion-color-light)}.apikey-create__label{margin:12px 0 4px;font-size:13px;font-weight:600;color:var(--ion-color-dark)}.apikey-perms{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;font-size:13px}.apikey-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}.apikey-error{margin:8px 0;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.apikey-secret__warn{font-size:.875rem;color:var(--ion-color-warning-shade, #b88600);margin:0 0 12px}.apikey-secret__box{padding:12px;border-radius:8px;background:var(--ion-color-dark);color:#fff;word-break:break-all;font-family:monospace;font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
49655
+ `, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.apikey-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.apikey-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:10px;border:1.5px solid var(--ion-color-light)}.apikey-row__main{display:flex;flex-direction:column}.apikey-row__name{font-weight:600;color:var(--ion-color-dark)}.apikey-row__meta{font-size:12px;color:var(--ion-color-medium-shade, #6b6e76)}.apikey-create{margin-top:8px;padding:16px;border-radius:12px;background:var(--ion-color-light)}.apikey-create__label{margin:12px 0 4px;font-size:13px;font-weight:600;color:var(--ion-color-dark)}.apikey-perms{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;font-size:13px}.apikey-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}.apikey-error{margin:8px 0;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.apikey-secret__warn{font-size:.875rem;color:var(--ion-color-warning-shade, #b88600);margin:0 0 12px}.apikey-secret__box{padding:12px;border-radius:8px;background:var(--ion-color-dark);color:#fff;word-break:break-all;font-family:monospace;font-size:13px}\n"], dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
49624
49656
  }
49625
49657
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeysModalComponent, decorators: [{
49626
49658
  type: Component,
49627
49659
  args: [{ selector: 'val-api-keys-modal', standalone: true, imports: [
49628
- FormsModule,
49629
49660
  IonHeader,
49630
49661
  IonToolbar,
49631
49662
  IonButtons,
49632
49663
  IonButton,
49633
49664
  IonContent,
49634
- IonInput,
49635
- IonCheckbox,
49636
49665
  IonSpinner,
49666
+ FormComponent,
49637
49667
  DisplayComponent,
49638
49668
  TextComponent,
49639
49669
  TitleComponent,
@@ -49699,45 +49729,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49699
49729
  <p class="apikey-error">{{ errorMsg() }}</p>
49700
49730
  }
49701
49731
 
49702
- <!-- Crear -->
49732
+ <!-- Crear — val-form (consistencia con el resto de modales del factory). -->
49703
49733
  @if (showCreate()) {
49704
49734
  <div class="apikey-create">
49705
- <ion-input
49706
- label="{{ t('nameLabel') }}"
49707
- labelPlacement="stacked"
49708
- [placeholder]="t('namePlaceholder')"
49709
- [(ngModel)]="name"
49710
- />
49711
- <p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
49712
- <div class="apikey-perms">
49713
- @for (p of availablePermissions(); track p) {
49714
- <ion-checkbox
49715
- labelPlacement="end"
49716
- [checked]="selectedPerms().has(p)"
49717
- (ionChange)="togglePerm(p, $event)"
49718
- >
49719
- {{ p }}
49720
- </ion-checkbox>
49721
- }
49722
- </div>
49723
- <ion-input
49724
- label="{{ t('expiresLabel') }}"
49725
- labelPlacement="stacked"
49726
- type="number"
49727
- [(ngModel)]="expiresInDays"
49728
- />
49729
- <div class="apikey-actions">
49730
- <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49731
- @if (submitting()) {
49732
- <ion-spinner name="dots" />&nbsp;{{ t('creating') }}
49733
- } @else {
49734
- {{ t('create') }}
49735
- }
49736
- </ion-button>
49737
- <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49738
- {{ t('cancel') }}
49739
- </ion-button>
49740
- </div>
49735
+ <val-form [props]="createForm()" (onSubmit)="onCreateSubmit($event)" />
49736
+ <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49737
+ {{ t('cancel') }}
49738
+ </ion-button>
49741
49739
  </div>
49742
49740
  } @else {
49743
49741
  <div class="apikey-actions">