easy-forms-core 1.1.6 → 1.1.7

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,16 @@ 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;
452
467
  /**
453
468
  * Renderiza campos normales
454
469
  */
package/dist/easy-form.js CHANGED
@@ -4763,6 +4763,11 @@ var EasyForm = class extends BrowserHTMLElement {
4763
4763
  this.isRendering = false;
4764
4764
  this.attemptsLock = null;
4765
4765
  this.lockCountdownInterval = null;
4766
+ /**
4767
+ * Plantillas de slots basados en atributo `row` en el light DOM.
4768
+ * Se inicializan una sola vez y se clonan en cada render.
4769
+ */
4770
+ this.slotTemplates = null;
4766
4771
  this.dependencyRenderTimeout = null;
4767
4772
  this.stateManager = new StateManager();
4768
4773
  this.shadow = this.attachShadow({ mode: "open" });
@@ -5207,6 +5212,16 @@ var EasyForm = class extends BrowserHTMLElement {
5207
5212
  * Retorna un objeto con los valores preservados
5208
5213
  */
5209
5214
  preserveCurrentValues() {
5215
+ let currentSchema = null;
5216
+ const templateName = this.template;
5217
+ if (templateName) {
5218
+ currentSchema = this.getSchemaFromTemplate(templateName);
5219
+ } else {
5220
+ currentSchema = this.schema;
5221
+ }
5222
+ if (!currentSchema) {
5223
+ return {};
5224
+ }
5210
5225
  const form = this.shadow.querySelector("form");
5211
5226
  const preservedValues = {};
5212
5227
  if (!form) return preservedValues;
@@ -5214,6 +5229,8 @@ var EasyForm = class extends BrowserHTMLElement {
5214
5229
  for (const input of inputs) {
5215
5230
  const name = input.getAttribute("name");
5216
5231
  if (!name) continue;
5232
+ const belongsToSchema = this.findFieldInSchema(currentSchema, name) !== null;
5233
+ if (!belongsToSchema) continue;
5217
5234
  let value;
5218
5235
  if (input instanceof HTMLInputElement) {
5219
5236
  if (input.type === "checkbox") {
@@ -5244,16 +5261,93 @@ var EasyForm = class extends BrowserHTMLElement {
5244
5261
  }
5245
5262
  return preservedValues;
5246
5263
  }
5264
+ /**
5265
+ * Inicializa las plantillas de slots a partir del light DOM.
5266
+ * 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).
5267
+ */
5268
+ initializeSlotTemplates() {
5269
+ if (this.slotTemplates !== null) return;
5270
+ const elements = [];
5271
+ for (const child of Array.from(this.children)) {
5272
+ if (child instanceof HTMLElement) {
5273
+ elements.push(child);
5274
+ }
5275
+ }
5276
+ if (elements.length === 0) {
5277
+ this.slotTemplates = [];
5278
+ return;
5279
+ }
5280
+ this.slotTemplates = elements.map((el) => {
5281
+ const raw = el.hasAttribute("row") ? el.getAttribute("row") : null;
5282
+ const parsed = raw != null && raw !== "" ? Number(raw) : NaN;
5283
+ const row = Number.isFinite(parsed) ? parsed : null;
5284
+ return {
5285
+ template: el.cloneNode(true),
5286
+ row
5287
+ };
5288
+ });
5289
+ }
5290
+ /**
5291
+ * Obtiene clones de slots agrupados por índice de fila efectivo.
5292
+ * Cualquier valor inválido o fuera de rango se normaliza a -1 (final del formulario).
5293
+ */
5294
+ getSlotClonesByRow(totalRows) {
5295
+ this.initializeSlotTemplates();
5296
+ const result = /* @__PURE__ */ new Map();
5297
+ if (!this.slotTemplates || this.slotTemplates.length === 0) {
5298
+ return result;
5299
+ }
5300
+ for (const { template, row } of this.slotTemplates) {
5301
+ let effectiveRow = typeof row === "number" ? row : -1;
5302
+ if (!Number.isFinite(effectiveRow)) {
5303
+ effectiveRow = -1;
5304
+ }
5305
+ if (effectiveRow < 0 || effectiveRow >= totalRows) {
5306
+ effectiveRow = -1;
5307
+ }
5308
+ const clone = template.cloneNode(true);
5309
+ const existing = result.get(effectiveRow) ?? [];
5310
+ existing.push(clone);
5311
+ result.set(effectiveRow, existing);
5312
+ }
5313
+ return result;
5314
+ }
5247
5315
  /**
5248
5316
  * Renderiza campos normales
5249
5317
  */
5250
5318
  renderFields(container, fields) {
5251
- for (const field of fields) {
5319
+ if (fields.length === 0) {
5320
+ const slotClones = this.getSlotClonesByRow(0);
5321
+ const endSlots2 = slotClones.get(-1);
5322
+ if (endSlots2 && endSlots2.length > 0) {
5323
+ for (const slotElement of endSlots2) {
5324
+ container.appendChild(slotElement);
5325
+ }
5326
+ }
5327
+ return;
5328
+ }
5329
+ const totalRows = fields.length;
5330
+ const slotClonesByRow = this.getSlotClonesByRow(totalRows);
5331
+ for (let rowIndex = 0; rowIndex < fields.length; rowIndex++) {
5332
+ const slotsForRow = slotClonesByRow.get(rowIndex);
5333
+ if (slotsForRow && slotsForRow.length > 0) {
5334
+ for (const slotElement of slotsForRow) {
5335
+ container.appendChild(slotElement);
5336
+ }
5337
+ slotClonesByRow.delete(rowIndex);
5338
+ }
5339
+ const field = fields[rowIndex];
5252
5340
  const fieldElement = this.renderField(field);
5253
5341
  if (fieldElement) {
5254
5342
  container.appendChild(fieldElement);
5255
5343
  }
5256
5344
  }
5345
+ const endSlots = slotClonesByRow.get(-1);
5346
+ if (endSlots && endSlots.length > 0) {
5347
+ for (const slotElement of endSlots) {
5348
+ container.appendChild(slotElement);
5349
+ }
5350
+ }
5257
5351
  }
5258
5352
  /**
5259
5353
  * Renderiza un campo
@@ -5511,12 +5605,7 @@ var EasyForm = class extends BrowserHTMLElement {
5511
5605
  const fieldsContainer = document.createElement("div");
5512
5606
  fieldsContainer.className = "easy-form-wizard-fields";
5513
5607
  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
- }
5608
+ this.renderFields(fieldsContainer, currentFields);
5520
5609
  wizardContainer.appendChild(fieldsContainer);
5521
5610
  const navContainer = document.createElement("div");
5522
5611
  navContainer.className = "easy-form-wizard-nav";