easy-forms-core 1.1.6 → 1.1.8

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/README.md CHANGED
@@ -108,6 +108,68 @@ form.schema = {
108
108
  }
109
109
  ```
110
110
 
111
+ ### Slots con inserción por fila (row)
112
+
113
+ Puedes insertar contenido HTML personalizado en posiciones concretas del formulario usando **hijos directos** del componente con el atributo `row`. El índice es 0-based (cada campo o fila del schema cuenta como una posición). Usa `row="-1"` para insertar al final, antes del botón de envío.
114
+
115
+ | Valor | Comportamiento |
116
+ |--------|-----------------|
117
+ | `row="0"` | Antes del primer campo |
118
+ | `row="1"` | Entre el primer y el segundo campo |
119
+ | `row="-1"` | Al final del formulario (antes del submit) |
120
+ | Sin `row` o valor inválido | Se trata como `-1` (al final) |
121
+
122
+ **HTML Vanilla**
123
+
124
+ ```html
125
+ <easy-form id="form">
126
+ <div row="0">Mensaje al inicio</div>
127
+ <div row="2">Mensaje después del segundo campo</div>
128
+ <div row="-1">Mensaje al final</div>
129
+ </easy-form>
130
+ ```
131
+
132
+ ```javascript
133
+ import 'easy-forms-core'
134
+
135
+ const form = document.querySelector('#form')
136
+ form.schema = {
137
+ fields: [
138
+ { type: 'text', name: 'name', label: 'Nombre' },
139
+ { type: 'email', name: 'email', label: 'Email' },
140
+ { type: 'textarea', name: 'message', label: 'Mensaje' }
141
+ ]
142
+ }
143
+ ```
144
+
145
+ **React**
146
+
147
+ ```tsx
148
+ import 'easy-forms-core'
149
+
150
+ function MyForm() {
151
+ return (
152
+ <easy-form schema={{ fields: [...] }}>
153
+ <div row="0">Contenido al inicio</div>
154
+ <div row="-1">Contenido al final</div>
155
+ </easy-form>
156
+ )
157
+ }
158
+ ```
159
+
160
+ **Vue**
161
+
162
+ ```vue
163
+ <template>
164
+ <easy-form :schema="schema">
165
+ <div row="0">Contenido al inicio</div>
166
+ <div row="-1">Contenido al final</div>
167
+ </easy-form>
168
+ </template>
169
+ ```
170
+
171
+ Los slots son solo visuales: los inputs dentro de un slot **no** forman parte del estado ni del envío del formulario. En formularios por pasos (wizard), el índice `row` es relativo al paso actual.
172
+
111
173
  ### React
112
174
 
113
175
  ```tsx
@@ -407,6 +469,7 @@ Puedes sobrescribir cualquier estilo usando las clases CSS del componente. Todas
407
469
  - Formularios anidados
408
470
  - Arrays dinámicos
409
471
  - **Rows (filas horizontales)** - Agrupa campos en filas
472
+ - **Slots (row)** - Inserta contenido HTML en posiciones concretas del formulario
410
473
  - **Datos iniciales** - Carga valores iniciales desde datos externos
411
474
  - Componentes visuales personalizables
412
475
  - Eventos de submit, change y error
@@ -361,6 +361,11 @@ declare class EasyForm extends BrowserHTMLElement {
361
361
  private isRendering;
362
362
  private attemptsLock;
363
363
  private lockCountdownInterval;
364
+ /**
365
+ * Plantillas de slots basados en atributo `row` en el light DOM.
366
+ * Se inicializan una sola vez y se clonan en cada render.
367
+ */
368
+ private slotTemplates;
364
369
  static get observedAttributes(): string[];
365
370
  constructor();
366
371
  /**
@@ -449,6 +454,21 @@ declare class EasyForm extends BrowserHTMLElement {
449
454
  * Retorna un objeto con los valores preservados
450
455
  */
451
456
  private preserveCurrentValues;
457
+ /**
458
+ * Inicializa las plantillas de slots a partir del light DOM.
459
+ * Cualquier hijo directo que sea HTMLElement se considera slot; si tiene atributo `row` se usa para la posición, si no se inserta al final (-1).
460
+ */
461
+ private initializeSlotTemplates;
462
+ /**
463
+ * Obtiene clones de slots agrupados por índice de fila efectivo.
464
+ * Cualquier valor inválido o fuera de rango se normaliza a -1 (final del formulario).
465
+ */
466
+ private getSlotClonesByRow;
467
+ /**
468
+ * Aplica estado disabled/loading a un clone de slot: deshabilita elementos
469
+ * interactivos y añade clase para estilos. Respeta el estado del formulario.
470
+ */
471
+ private applySlotDisabledState;
452
472
  /**
453
473
  * Renderiza campos normales
454
474
  */
package/dist/easy-form.js CHANGED
@@ -741,14 +741,20 @@ function getBaseStyles(colors) {
741
741
  }
742
742
  /* Disabled State */
743
743
  .easy-form-disabled,
744
- .easy-form-disabled * {
744
+ .easy-form-disabled *,
745
+ .easy-form-slot-disabled,
746
+ .easy-form-slot-disabled * {
745
747
  pointer-events: none;
746
748
  opacity: 0.6;
747
749
  }
748
750
  .easy-form-disabled input,
749
751
  .easy-form-disabled textarea,
750
752
  .easy-form-disabled select,
751
- .easy-form-disabled button {
753
+ .easy-form-disabled button,
754
+ .easy-form-slot-disabled input,
755
+ .easy-form-slot-disabled textarea,
756
+ .easy-form-slot-disabled select,
757
+ .easy-form-slot-disabled button {
752
758
  cursor: not-allowed;
753
759
  }
754
760
  .easy-form-input-disabled {
@@ -4763,6 +4769,11 @@ var EasyForm = class extends BrowserHTMLElement {
4763
4769
  this.isRendering = false;
4764
4770
  this.attemptsLock = null;
4765
4771
  this.lockCountdownInterval = null;
4772
+ /**
4773
+ * Plantillas de slots basados en atributo `row` en el light DOM.
4774
+ * Se inicializan una sola vez y se clonan en cada render.
4775
+ */
4776
+ this.slotTemplates = null;
4766
4777
  this.dependencyRenderTimeout = null;
4767
4778
  this.stateManager = new StateManager();
4768
4779
  this.shadow = this.attachShadow({ mode: "open" });
@@ -5207,6 +5218,16 @@ var EasyForm = class extends BrowserHTMLElement {
5207
5218
  * Retorna un objeto con los valores preservados
5208
5219
  */
5209
5220
  preserveCurrentValues() {
5221
+ let currentSchema = null;
5222
+ const templateName = this.template;
5223
+ if (templateName) {
5224
+ currentSchema = this.getSchemaFromTemplate(templateName);
5225
+ } else {
5226
+ currentSchema = this.schema;
5227
+ }
5228
+ if (!currentSchema) {
5229
+ return {};
5230
+ }
5210
5231
  const form = this.shadow.querySelector("form");
5211
5232
  const preservedValues = {};
5212
5233
  if (!form) return preservedValues;
@@ -5214,6 +5235,8 @@ var EasyForm = class extends BrowserHTMLElement {
5214
5235
  for (const input of inputs) {
5215
5236
  const name = input.getAttribute("name");
5216
5237
  if (!name) continue;
5238
+ const belongsToSchema = this.findFieldInSchema(currentSchema, name) !== null;
5239
+ if (!belongsToSchema) continue;
5217
5240
  let value;
5218
5241
  if (input instanceof HTMLInputElement) {
5219
5242
  if (input.type === "checkbox") {
@@ -5244,16 +5267,110 @@ var EasyForm = class extends BrowserHTMLElement {
5244
5267
  }
5245
5268
  return preservedValues;
5246
5269
  }
5270
+ /**
5271
+ * Inicializa las plantillas de slots a partir del light DOM.
5272
+ * Cualquier hijo directo que sea HTMLElement se considera slot; si tiene atributo `row` se usa para la posición, si no se inserta al final (-1).
5273
+ */
5274
+ initializeSlotTemplates() {
5275
+ if (this.slotTemplates !== null) return;
5276
+ const elements = [];
5277
+ for (const child of Array.from(this.children)) {
5278
+ if (child instanceof HTMLElement) {
5279
+ elements.push(child);
5280
+ }
5281
+ }
5282
+ if (elements.length === 0) {
5283
+ this.slotTemplates = [];
5284
+ return;
5285
+ }
5286
+ this.slotTemplates = elements.map((el) => {
5287
+ const raw = el.hasAttribute("row") ? el.getAttribute("row") : null;
5288
+ const parsed = raw != null && raw !== "" ? Number(raw) : NaN;
5289
+ const row = Number.isFinite(parsed) ? parsed : null;
5290
+ return {
5291
+ template: el.cloneNode(true),
5292
+ row
5293
+ };
5294
+ });
5295
+ }
5296
+ /**
5297
+ * Obtiene clones de slots agrupados por índice de fila efectivo.
5298
+ * Cualquier valor inválido o fuera de rango se normaliza a -1 (final del formulario).
5299
+ */
5300
+ getSlotClonesByRow(totalRows) {
5301
+ this.initializeSlotTemplates();
5302
+ const result = /* @__PURE__ */ new Map();
5303
+ if (!this.slotTemplates || this.slotTemplates.length === 0) {
5304
+ return result;
5305
+ }
5306
+ for (const { template, row } of this.slotTemplates) {
5307
+ let effectiveRow = typeof row === "number" ? row : -1;
5308
+ if (!Number.isFinite(effectiveRow)) {
5309
+ effectiveRow = -1;
5310
+ }
5311
+ if (effectiveRow < 0 || effectiveRow >= totalRows) {
5312
+ effectiveRow = -1;
5313
+ }
5314
+ const clone = template.cloneNode(true);
5315
+ const existing = result.get(effectiveRow) ?? [];
5316
+ existing.push(clone);
5317
+ result.set(effectiveRow, existing);
5318
+ }
5319
+ return result;
5320
+ }
5321
+ /**
5322
+ * Aplica estado disabled/loading a un clone de slot: deshabilita elementos
5323
+ * interactivos y añade clase para estilos. Respeta el estado del formulario.
5324
+ */
5325
+ applySlotDisabledState(slotElement) {
5326
+ if (!this.disabled && !this.loading) return;
5327
+ slotElement.classList.add("easy-form-slot-disabled");
5328
+ const interactives = slotElement.querySelectorAll(
5329
+ "input, textarea, select, button"
5330
+ );
5331
+ interactives.forEach((el) => {
5332
+ if ("disabled" in el) el.disabled = true;
5333
+ });
5334
+ }
5247
5335
  /**
5248
5336
  * Renderiza campos normales
5249
5337
  */
5250
5338
  renderFields(container, fields) {
5251
- for (const field of fields) {
5339
+ if (fields.length === 0) {
5340
+ const slotClones = this.getSlotClonesByRow(0);
5341
+ const endSlots2 = slotClones.get(-1);
5342
+ if (endSlots2 && endSlots2.length > 0) {
5343
+ for (const slotElement of endSlots2) {
5344
+ this.applySlotDisabledState(slotElement);
5345
+ container.appendChild(slotElement);
5346
+ }
5347
+ }
5348
+ return;
5349
+ }
5350
+ const totalRows = fields.length;
5351
+ const slotClonesByRow = this.getSlotClonesByRow(totalRows);
5352
+ for (let rowIndex = 0; rowIndex < fields.length; rowIndex++) {
5353
+ const slotsForRow = slotClonesByRow.get(rowIndex);
5354
+ if (slotsForRow && slotsForRow.length > 0) {
5355
+ for (const slotElement of slotsForRow) {
5356
+ this.applySlotDisabledState(slotElement);
5357
+ container.appendChild(slotElement);
5358
+ }
5359
+ slotClonesByRow.delete(rowIndex);
5360
+ }
5361
+ const field = fields[rowIndex];
5252
5362
  const fieldElement = this.renderField(field);
5253
5363
  if (fieldElement) {
5254
5364
  container.appendChild(fieldElement);
5255
5365
  }
5256
5366
  }
5367
+ const endSlots = slotClonesByRow.get(-1);
5368
+ if (endSlots && endSlots.length > 0) {
5369
+ for (const slotElement of endSlots) {
5370
+ this.applySlotDisabledState(slotElement);
5371
+ container.appendChild(slotElement);
5372
+ }
5373
+ }
5257
5374
  }
5258
5375
  /**
5259
5376
  * Renderiza un campo
@@ -5511,12 +5628,7 @@ var EasyForm = class extends BrowserHTMLElement {
5511
5628
  const fieldsContainer = document.createElement("div");
5512
5629
  fieldsContainer.className = "easy-form-wizard-fields";
5513
5630
  const currentFields = this.stateManager.getCurrentStepFields();
5514
- for (const field of currentFields) {
5515
- const fieldElement = this.renderField(field);
5516
- if (fieldElement) {
5517
- fieldsContainer.appendChild(fieldElement);
5518
- }
5519
- }
5631
+ this.renderFields(fieldsContainer, currentFields);
5520
5632
  wizardContainer.appendChild(fieldsContainer);
5521
5633
  const navContainer = document.createElement("div");
5522
5634
  navContainer.className = "easy-form-wizard-nav";