codexly-ui 0.4.9 → 0.5.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.
@@ -2321,6 +2321,11 @@ class ClxTooltipDirective {
2321
2321
  host.addEventListener('mouseleave', () => this._scheduleHide(), { signal });
2322
2322
  host.addEventListener('focus', () => this._scheduleShow(), { signal });
2323
2323
  host.addEventListener('blur', () => this._scheduleHide(), { signal });
2324
+ // Touch devices fire 'mouseenter'/'focus' on tap but never a real
2325
+ // 'mouseleave' — hide immediately once the tap completes instead of
2326
+ // leaving the bubble stuck until the next unrelated touch.
2327
+ host.addEventListener('touchend', () => this._hide(), { signal });
2328
+ host.addEventListener('touchcancel', () => this._hide(), { signal });
2324
2329
  this._destroyRef.onDestroy(() => {
2325
2330
  this._destroyed = true;
2326
2331
  this._abort.abort();
@@ -4920,6 +4925,9 @@ function resolveFileTypeVisual(name, mimeType) {
4920
4925
  return FILE_TYPE_BY_EXTENSION[ext] ?? FILE_TYPE_DEFAULT;
4921
4926
  }
4922
4927
 
4928
+ const DEFAULT_PRIMARY_ACTION = () => ({
4929
+ icon: 'close', color: 'red', tooltip: 'Eliminar',
4930
+ });
4923
4931
  class ClxUploadComponent {
4924
4932
  _themeSvc = inject(ClxThemeService);
4925
4933
  // ── Inputs ─────────────────────────────────────────────────────────────────
@@ -4936,10 +4944,18 @@ class ClxUploadComponent {
4936
4944
  * preview grid as newly-selected local files. The component never deletes these itself —
4937
4945
  * removing one only emits `existingFileRemove`, letting the caller decide (e.g. call the API). */
4938
4946
  existingFiles = input([], ...(ngDevMode ? [{ debugName: "existingFiles" }] : /* istanbul ignore next */ []));
4947
+ /** Resolves the badge button shown top-right on each thumbnail. Defaults to a red "Eliminar"
4948
+ * button wired to the built-in remove behavior. Return `null` for an item to hide it. */
4949
+ primaryAction = input(DEFAULT_PRIMARY_ACTION, ...(ngDevMode ? [{ debugName: "primaryAction" }] : /* istanbul ignore next */ []));
4950
+ /** Resolves an optional second badge button (bottom-right on each thumbnail). Hidden by
4951
+ * default — the caller owns what clicking it means (e.g. opening a settings modal); this
4952
+ * component only reports which item was clicked via `secondaryActionClick`. */
4953
+ secondaryAction = input(null, ...(ngDevMode ? [{ debugName: "secondaryAction" }] : /* istanbul ignore next */ []));
4939
4954
  // ── Outputs ────────────────────────────────────────────────────────────────
4940
4955
  onUploadError = output();
4941
4956
  onUploadSuccess = output();
4942
4957
  existingFileRemove = output();
4958
+ secondaryActionClick = output();
4943
4959
  // ── Internal state ─────────────────────────────────────────────────────────
4944
4960
  _files = signal([], ...(ngDevMode ? [{ debugName: "_files" }] : /* istanbul ignore next */ []));
4945
4961
  _isDragging = signal(false, ...(ngDevMode ? [{ debugName: "_isDragging" }] : /* istanbul ignore next */ []));
@@ -5104,6 +5120,18 @@ class ClxUploadComponent {
5104
5120
  if (files?.length)
5105
5121
  this._addFiles(files);
5106
5122
  }
5123
+ _resolvePrimaryAction(item) {
5124
+ return this.primaryAction()(item) ?? null;
5125
+ }
5126
+ _resolveSecondaryAction(item) {
5127
+ const resolver = this.secondaryAction();
5128
+ return resolver ? (resolver(item) ?? null) : null;
5129
+ }
5130
+ /** Built-in behavior for the default primary action (remove). Callers who override
5131
+ * `primaryAction` only for its visuals still get this same removal behavior. */
5132
+ _onPrimaryAction(item) {
5133
+ this._removeItem(item);
5134
+ }
5107
5135
  _removeItem(item) {
5108
5136
  if (item.kind === 'existing') {
5109
5137
  this.existingFileRemove.emit(item.existing.id);
@@ -5217,7 +5245,7 @@ class ClxUploadComponent {
5217
5245
  return `${(bytes / 1_048_576).toFixed(1)} MB`;
5218
5246
  }
5219
5247
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5220
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxUploadComponent, isStandalone: true, selector: "clx-upload", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, existingFiles: { classPropertyName: "existingFiles", publicName: "existingFiles", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onUploadError: "onUploadError", onUploadSuccess: "onUploadSuccess", existingFileRemove: "existingFileRemove" }, host: { classAttribute: "block w-full" }, viewQueries: [{ propertyName: "_fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
5248
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.15", type: ClxUploadComponent, isStandalone: true, selector: "clx-upload", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxFileSize: { classPropertyName: "maxFileSize", publicName: "maxFileSize", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, existingFiles: { classPropertyName: "existingFiles", publicName: "existingFiles", isSignal: true, isRequired: false, transformFunction: null }, primaryAction: { classPropertyName: "primaryAction", publicName: "primaryAction", isSignal: true, isRequired: false, transformFunction: null }, secondaryAction: { classPropertyName: "secondaryAction", publicName: "secondaryAction", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onUploadError: "onUploadError", onUploadSuccess: "onUploadSuccess", existingFileRemove: "existingFileRemove", secondaryActionClick: "secondaryActionClick" }, host: { classAttribute: "block w-full" }, viewQueries: [{ propertyName: "_fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
5221
5249
  <!-- Hidden file input -->
5222
5250
  <input
5223
5251
  #fileInput
@@ -5286,19 +5314,41 @@ class ClxUploadComponent {
5286
5314
  }
5287
5315
  </div>
5288
5316
 
5289
- <button
5290
- clx-button
5291
- type="button"
5292
- variant="solid"
5293
- color="red"
5294
- shape="circle"
5295
- size="xxs"
5296
- icon="close"
5297
- [iconOnly]="true"
5298
- class="absolute top-0 right-0 -translate-y-1/2 translate-x-1/2 shadow-md z-10"
5299
- [disabled]="_disabled()"
5300
- (click)="_removeItem(item); $event.stopPropagation()">
5301
- </button>
5317
+ @let primaryAction = _resolvePrimaryAction(item);
5318
+ @if (primaryAction) {
5319
+ <button
5320
+ clx-button
5321
+ type="button"
5322
+ variant="solid"
5323
+ [color]="primaryAction.color ?? 'red'"
5324
+ shape="circle"
5325
+ size="xxs"
5326
+ [icon]="primaryAction.icon"
5327
+ [iconOnly]="true"
5328
+ [clxTooltip]="primaryAction.tooltip ?? ''"
5329
+ class="absolute top-0 right-0 -translate-y-1/2 translate-x-1/2 shadow-md z-10"
5330
+ [disabled]="_disabled()"
5331
+ (click)="_onPrimaryAction(item); $event.stopPropagation()">
5332
+ </button>
5333
+ }
5334
+
5335
+ @let secondaryAction = _resolveSecondaryAction(item);
5336
+ @if (secondaryAction) {
5337
+ <button
5338
+ clx-button
5339
+ type="button"
5340
+ variant="solid"
5341
+ [color]="secondaryAction.color ?? 'slate'"
5342
+ shape="circle"
5343
+ size="xxs"
5344
+ [icon]="secondaryAction.icon"
5345
+ [iconOnly]="true"
5346
+ [clxTooltip]="secondaryAction.tooltip ?? ''"
5347
+ class="absolute bottom-0 right-0 translate-y-1/2 translate-x-1/2 shadow-md z-10"
5348
+ [disabled]="_disabled()"
5349
+ (click)="secondaryActionClick.emit(item); $event.stopPropagation()">
5350
+ </button>
5351
+ }
5302
5352
 
5303
5353
  <div class="px-1.5 py-1 w-full min-w-0">
5304
5354
  <p class="text-[11px] leading-tight text-clx-text-label font-medium truncate" [title]="item.name">{{ item.name }}</p>
@@ -5318,14 +5368,14 @@ class ClxUploadComponent {
5318
5368
  {{ _errorMessage() }}
5319
5369
  </p>
5320
5370
  }
5321
- `, isInline: true, dependencies: [{ kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5371
+ `, isInline: true, dependencies: [{ kind: "component", type: ClxIconComponent, selector: "span[clx-icon]", inputs: ["name", "size", "color", "fill"] }, { kind: "component", type: ClxButtonComponent, selector: "button[clx-button], a[clx-button]", inputs: ["variant", "color", "size", "shape", "loading", "disabled", "block", "icon", "iconPosition", "iconOnly", "badge", "badgeColor"] }, { kind: "directive", type: ClxTooltipDirective, selector: "[clxTooltip]", inputs: ["clxTooltip", "clxTooltipPosition", "clxTooltipColor", "clxTooltipSize", "clxTooltipDelay"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
5322
5372
  }
5323
5373
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImport: i0, type: ClxUploadComponent, decorators: [{
5324
5374
  type: Component,
5325
5375
  args: [{
5326
5376
  selector: 'clx-upload',
5327
5377
  standalone: true,
5328
- imports: [ClxIconComponent, ClxButtonComponent],
5378
+ imports: [ClxIconComponent, ClxButtonComponent, ClxTooltipDirective],
5329
5379
  template: `
5330
5380
  <!-- Hidden file input -->
5331
5381
  <input
@@ -5395,19 +5445,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
5395
5445
  }
5396
5446
  </div>
5397
5447
 
5398
- <button
5399
- clx-button
5400
- type="button"
5401
- variant="solid"
5402
- color="red"
5403
- shape="circle"
5404
- size="xxs"
5405
- icon="close"
5406
- [iconOnly]="true"
5407
- class="absolute top-0 right-0 -translate-y-1/2 translate-x-1/2 shadow-md z-10"
5408
- [disabled]="_disabled()"
5409
- (click)="_removeItem(item); $event.stopPropagation()">
5410
- </button>
5448
+ @let primaryAction = _resolvePrimaryAction(item);
5449
+ @if (primaryAction) {
5450
+ <button
5451
+ clx-button
5452
+ type="button"
5453
+ variant="solid"
5454
+ [color]="primaryAction.color ?? 'red'"
5455
+ shape="circle"
5456
+ size="xxs"
5457
+ [icon]="primaryAction.icon"
5458
+ [iconOnly]="true"
5459
+ [clxTooltip]="primaryAction.tooltip ?? ''"
5460
+ class="absolute top-0 right-0 -translate-y-1/2 translate-x-1/2 shadow-md z-10"
5461
+ [disabled]="_disabled()"
5462
+ (click)="_onPrimaryAction(item); $event.stopPropagation()">
5463
+ </button>
5464
+ }
5465
+
5466
+ @let secondaryAction = _resolveSecondaryAction(item);
5467
+ @if (secondaryAction) {
5468
+ <button
5469
+ clx-button
5470
+ type="button"
5471
+ variant="solid"
5472
+ [color]="secondaryAction.color ?? 'slate'"
5473
+ shape="circle"
5474
+ size="xxs"
5475
+ [icon]="secondaryAction.icon"
5476
+ [iconOnly]="true"
5477
+ [clxTooltip]="secondaryAction.tooltip ?? ''"
5478
+ class="absolute bottom-0 right-0 translate-y-1/2 translate-x-1/2 shadow-md z-10"
5479
+ [disabled]="_disabled()"
5480
+ (click)="secondaryActionClick.emit(item); $event.stopPropagation()">
5481
+ </button>
5482
+ }
5411
5483
 
5412
5484
  <div class="px-1.5 py-1 w-full min-w-0">
5413
5485
  <p class="text-[11px] leading-tight text-clx-text-label font-medium truncate" [title]="item.name">{{ item.name }}</p>
@@ -5434,7 +5506,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.15", ngImpo
5434
5506
  class: 'block w-full',
5435
5507
  },
5436
5508
  }]
5437
- }], ctorParameters: () => [], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], existingFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFiles", required: false }] }], onUploadError: [{ type: i0.Output, args: ["onUploadError"] }], onUploadSuccess: [{ type: i0.Output, args: ["onUploadSuccess"] }], existingFileRemove: [{ type: i0.Output, args: ["existingFileRemove"] }], _fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
5509
+ }], ctorParameters: () => [], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxFileSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFileSize", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], existingFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingFiles", required: false }] }], primaryAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryAction", required: false }] }], secondaryAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryAction", required: false }] }], onUploadError: [{ type: i0.Output, args: ["onUploadError"] }], onUploadSuccess: [{ type: i0.Output, args: ["onUploadSuccess"] }], existingFileRemove: [{ type: i0.Output, args: ["existingFileRemove"] }], secondaryActionClick: [{ type: i0.Output, args: ["secondaryActionClick"] }], _fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
5438
5510
 
5439
5511
  const COLOR_PICKER_SIZE_MAP = {
5440
5512
  sm: { minH: 'min-h-8', text: 'text-sm', label: 'text-xs font-medium', hint: 'text-xs', px: 'px-2.5', swatchSz: 'w-4 h-4', btnSize: 'xs' },