@erplora/outfitkit 0.1.13 → 0.1.14

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.
@@ -39,6 +39,13 @@ export interface DataTableAction {
39
39
  icon?: string;
40
40
  /** Color Ionic del botón (p.ej. 'danger', 'primary'). */
41
41
  color?: string;
42
+ /** (opcional, por fila) Si devuelve `true` para una fila, el botón de esa acción se renderiza
43
+ * deshabilitado (`?disabled` + `aria-disabled="true"`). */
44
+ disabled?: (row: Record<string, unknown>) => boolean;
45
+ /** (opcional, por fila) Si devuelve `true` para una fila, el botón muestra un
46
+ * `<ion-spinner name="dots">` en lugar del icono/label Y queda deshabilitado
47
+ * (una acción en curso no debe ser re-clicable). */
48
+ loading?: (row: Record<string, unknown>) => boolean;
42
49
  }
43
50
  /** Acción primaria de la topbar (botón destacado). Emite el evento `primaryAction`. */
44
51
  export interface DataTablePrimaryAction {
@@ -304,6 +304,10 @@ class OkDataTable extends LitElement {
304
304
  .empty .empty-ic { display: grid; place-items: center; width: 3.25rem; height: 3.25rem; border-radius: 999px; background: var(--header-background); font-size: 26px; }
305
305
 
306
306
  .actions { display: flex; gap: 0.25rem; justify-content: flex-end; }
307
+ /* Spinner de acción en curso (loading): contenido dentro del ion-button small (Ionic lo fija
308
+ * a 28px en el :host, por eso width/height y no font-size). Cubre tabla y tarjetas: los
309
+ * botones de fila siempre van dentro de .actions. */
310
+ .actions ion-spinner { width: 18px; height: 18px; }
307
311
 
308
312
  /* ── Pie: contador + paginación ──────────────────────────────────────────────────────── */
309
313
  .pager { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.55rem 1rem; border-top: 1px solid var(--border-color); background: var(--header-background); font-size: 12.5px; color: var(--color-muted); }
@@ -813,16 +817,22 @@ class OkDataTable extends LitElement {
813
817
  return html`
814
818
  <div class="actions">
815
819
  ${this.actions.map(
816
- (a) => html`
820
+ (a) => {
821
+ const loading = a.loading?.(row) === true;
822
+ const disabled = loading || a.disabled?.(row) === true;
823
+ return html`
817
824
  <ion-button
818
825
  size="small"
819
826
  fill="clear"
820
827
  color=${a.color ?? "medium"}
828
+ ?disabled=${disabled}
829
+ aria-disabled=${disabled ? "true" : nothing}
821
830
  @click=${() => this.emit("rowAction", { actionId: a.id, row })}
822
831
  >
823
- ${a.icon ? html`<ion-icon slot="icon-only" name=${a.icon}></ion-icon>` : a.label}
832
+ ${loading ? html`<ion-spinner slot="icon-only" name="dots"></ion-spinner>` : a.icon ? html`<ion-icon slot="icon-only" name=${a.icon}></ion-icon>` : a.label}
824
833
  </ion-button>
825
- `
834
+ `;
835
+ }
826
836
  )}
827
837
  </div>
828
838
  `;
@@ -1229,6 +1229,10 @@ class _ extends g {
1229
1229
  .empty .empty-ic { display: grid; place-items: center; width: 3.25rem; height: 3.25rem; border-radius: 999px; background: var(--header-background); font-size: 26px; }
1230
1230
 
1231
1231
  .actions { display: flex; gap: 0.25rem; justify-content: flex-end; }
1232
+ /* Spinner de acción en curso (loading): contenido dentro del ion-button small (Ionic lo fija
1233
+ * a 28px en el :host, por eso width/height y no font-size). Cubre tabla y tarjetas: los
1234
+ * botones de fila siempre van dentro de .actions. */
1235
+ .actions ion-spinner { width: 18px; height: 18px; }
1232
1236
 
1233
1237
  /* ── Pie: contador + paginación ──────────────────────────────────────────────────────── */
1234
1238
  .pager { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.55rem 1rem; border-top: 1px solid var(--border-color); background: var(--header-background); font-size: 12.5px; color: var(--color-muted); }
@@ -1636,16 +1640,21 @@ class _ extends g {
1636
1640
  return this.actions.length ? s`
1637
1641
  <div class="actions">
1638
1642
  ${this.actions.map(
1639
- (t) => s`
1643
+ (t) => {
1644
+ const r = t.loading?.(e) === !0, o = r || t.disabled?.(e) === !0;
1645
+ return s`
1640
1646
  <ion-button
1641
1647
  size="small"
1642
1648
  fill="clear"
1643
1649
  color=${t.color ?? "medium"}
1650
+ ?disabled=${o}
1651
+ aria-disabled=${o ? "true" : m}
1644
1652
  @click=${() => this.emit("rowAction", { actionId: t.id, row: e })}
1645
1653
  >
1646
- ${t.icon ? s`<ion-icon slot="icon-only" name=${t.icon}></ion-icon>` : t.label}
1654
+ ${r ? s`<ion-spinner slot="icon-only" name="dots"></ion-spinner>` : t.icon ? s`<ion-icon slot="icon-only" name=${t.icon}></ion-icon>` : t.label}
1647
1655
  </ion-button>
1648
- `
1656
+ `;
1657
+ }
1649
1658
  )}
1650
1659
  </div>
1651
1660
  ` : m;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erplora/outfitkit",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "OutfitKit — librería de Web Components (Lit) que CONSTRUYE lo que Ionic no tiene (tree, data-table rica, inline-feedback, kpi/stat, stepper/wizard, calendar, kanban…) sobre primitivos de Ionic. Ionic es la base; OutfitKit cubre los huecos. npm + CDN, imports individuales, CSP-safe. Tema vía tokens --ok-* (fallback a --ion-*).",
5
5
  "type": "module",
6
6
  "license": "MIT",