easy-forms-core 1.1.7 → 1.1.9

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
@@ -470,6 +470,7 @@ Puedes sobrescribir cualquier estilo usando las clases CSS del componente. Todas
470
470
  - Arrays dinámicos
471
471
  - **Rows (filas horizontales)** - Agrupa campos en filas
472
472
  - **Slots (row)** - Inserta contenido HTML en posiciones concretas del formulario
473
+ - **Campo password** - Opciones `showToggle` (ver/ocultar) y `characterSeparated` (entrada por caracteres, tipo OTP)
473
474
  - **Datos iniciales** - Carga valores iniciales desde datos externos
474
475
  - Componentes visuales personalizables
475
476
  - Eventos de submit, change y error
@@ -125,7 +125,17 @@ interface BaseField {
125
125
  * Campo de texto
126
126
  */
127
127
  interface TextField extends BaseField {
128
- type: 'text' | 'email' | 'password';
128
+ type: 'text' | 'email';
129
+ }
130
+ /**
131
+ * Campo password (con opciones de visibilidad y modo carácter separado)
132
+ */
133
+ interface PasswordField extends BaseField {
134
+ type: 'password';
135
+ /** Mostrar botón para alternar visibilidad del texto */
136
+ showToggle?: boolean;
137
+ /** Modo carácter separado: true = longitud por defecto 6, number = cantidad de cajas */
138
+ characterSeparated?: boolean | number;
129
139
  }
130
140
  /**
131
141
  * Campo numérico
@@ -277,7 +287,7 @@ interface OTPField extends BaseField {
277
287
  /**
278
288
  * Unión de todos los tipos de campos
279
289
  */
280
- type Field = TextField | NumberField | TextareaField | SelectField | CheckboxField | RadioField | SwitchField | DateField | FileField | ArrayField | GroupField | RowField | CustomField | QuantityField | AccordionSelectField | ImageGridSelectField | OTPField;
290
+ type Field = TextField | PasswordField | NumberField | TextareaField | SelectField | CheckboxField | RadioField | SwitchField | DateField | FileField | ArrayField | GroupField | RowField | CustomField | QuantityField | AccordionSelectField | ImageGridSelectField | OTPField;
281
291
  /**
282
292
  * Step para formularios wizard
283
293
  */
@@ -464,6 +474,11 @@ declare class EasyForm extends BrowserHTMLElement {
464
474
  * Cualquier valor inválido o fuera de rango se normaliza a -1 (final del formulario).
465
475
  */
466
476
  private getSlotClonesByRow;
477
+ /**
478
+ * Aplica estado disabled/loading a un clone de slot: deshabilita elementos
479
+ * interactivos y añade clase para estilos. Respeta el estado del formulario.
480
+ */
481
+ private applySlotDisabledState;
467
482
  /**
468
483
  * Renderiza campos normales
469
484
  */
package/dist/easy-form.js CHANGED
@@ -689,6 +689,62 @@ function getBaseStyles(colors) {
689
689
  .easy-form-otp-input:invalid {
690
690
  border-color: var(--easy-form-error);
691
691
  }
692
+ /* Password inner (input + toggle) */
693
+ .easy-form-password-inner {
694
+ display: flex;
695
+ align-items: center;
696
+ gap: 0.5rem;
697
+ }
698
+ .easy-form-password-inner .easy-form-password-input {
699
+ flex: 1;
700
+ min-width: 0;
701
+ }
702
+ .easy-form-password-toggle {
703
+ flex-shrink: 0;
704
+ display: inline-flex;
705
+ align-items: center;
706
+ justify-content: center;
707
+ padding: 0.35rem;
708
+ background: transparent;
709
+ border: 1px solid var(--easy-form-border);
710
+ border-radius: 4px;
711
+ cursor: pointer;
712
+ color: var(--easy-form-text);
713
+ }
714
+ .easy-form-password-toggle:hover {
715
+ border-color: var(--easy-form-primary);
716
+ color: var(--easy-form-primary);
717
+ }
718
+ .easy-form-password-toggle:focus {
719
+ outline: none;
720
+ box-shadow: 0 0 0 2px var(--easy-form-primary);
721
+ }
722
+ /* Password car\xE1cter separado */
723
+ .easy-form-password-separated {
724
+ display: flex;
725
+ flex-wrap: nowrap;
726
+ gap: 0.5rem;
727
+ align-items: center;
728
+ }
729
+ .easy-form-password-separated-input {
730
+ width: 2.5rem;
731
+ height: 2.5rem;
732
+ text-align: center;
733
+ font-size: 1.25rem;
734
+ border: 2px solid var(--easy-form-border);
735
+ border-radius: 4px;
736
+ transition: border-color 0.2s ease;
737
+ }
738
+ .easy-form-password-separated-input:focus {
739
+ border-color: var(--easy-form-primary);
740
+ outline: none;
741
+ }
742
+ .easy-form-password-separated-toggle {
743
+ flex-shrink: 0;
744
+ }
745
+ .easy-form-password-separated-toggle .easy-form-password-toggle {
746
+ padding: 0.35rem;
747
+ }
692
748
  /* Loading Overlay (sobre el formulario) */
693
749
  .easy-form-loading-overlay {
694
750
  position: absolute;
@@ -741,14 +797,20 @@ function getBaseStyles(colors) {
741
797
  }
742
798
  /* Disabled State */
743
799
  .easy-form-disabled,
744
- .easy-form-disabled * {
800
+ .easy-form-disabled *,
801
+ .easy-form-slot-disabled,
802
+ .easy-form-slot-disabled * {
745
803
  pointer-events: none;
746
804
  opacity: 0.6;
747
805
  }
748
806
  .easy-form-disabled input,
749
807
  .easy-form-disabled textarea,
750
808
  .easy-form-disabled select,
751
- .easy-form-disabled button {
809
+ .easy-form-disabled button,
810
+ .easy-form-slot-disabled input,
811
+ .easy-form-slot-disabled textarea,
812
+ .easy-form-slot-disabled select,
813
+ .easy-form-slot-disabled button {
752
814
  cursor: not-allowed;
753
815
  }
754
816
  .easy-form-input-disabled {
@@ -4190,13 +4252,159 @@ var OTPInput = class extends BaseInput {
4190
4252
  }
4191
4253
  };
4192
4254
 
4255
+ // src/components/inputs/password-input.ts
4256
+ var DEFAULT_SEPARATED_LENGTH = 6;
4257
+ function eyeSvg(visible) {
4258
+ if (visible) {
4259
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>`;
4260
+ }
4261
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>`;
4262
+ }
4263
+ var PasswordInput = class extends BaseInput {
4264
+ constructor() {
4265
+ super(...arguments);
4266
+ this.inputs = [];
4267
+ this.visible = false;
4268
+ }
4269
+ get passwordField() {
4270
+ return this.field;
4271
+ }
4272
+ render() {
4273
+ const pf = this.passwordField;
4274
+ const useSeparated = !!pf.characterSeparated;
4275
+ const length = useSeparated ? typeof pf.characterSeparated === "number" ? pf.characterSeparated : DEFAULT_SEPARATED_LENGTH : 0;
4276
+ if (useSeparated) {
4277
+ return this.createFieldContainer(this.renderSeparated(length));
4278
+ }
4279
+ return this.createFieldContainer(this.renderSingle());
4280
+ }
4281
+ renderSingle() {
4282
+ const pf = this.passwordField;
4283
+ const showToggle = !!pf.showToggle;
4284
+ const input = document.createElement("input");
4285
+ input.type = "password";
4286
+ input.value = this.value ?? "";
4287
+ input.id = this.getFieldId();
4288
+ this.applyCommonProps(input);
4289
+ if (this.field.placeholder) input.placeholder = this.field.placeholder;
4290
+ input.addEventListener("input", (e) => {
4291
+ this.onChange(e.target.value);
4292
+ });
4293
+ input.addEventListener("blur", () => this.onBlur());
4294
+ if (!showToggle) {
4295
+ return input;
4296
+ }
4297
+ const wrapper = document.createElement("div");
4298
+ wrapper.className = "easy-form-password-inner";
4299
+ input.classList.add("easy-form-password-input");
4300
+ wrapper.appendChild(input);
4301
+ const btn = document.createElement("button");
4302
+ btn.type = "button";
4303
+ btn.className = "easy-form-password-toggle";
4304
+ btn.setAttribute("aria-label", "Mostrar contrase\xF1a");
4305
+ btn.innerHTML = eyeSvg(false);
4306
+ btn.addEventListener("click", () => {
4307
+ this.visible = !this.visible;
4308
+ input.type = this.visible ? "text" : "password";
4309
+ btn.setAttribute("aria-label", this.visible ? "Ocultar contrase\xF1a" : "Mostrar contrase\xF1a");
4310
+ btn.innerHTML = eyeSvg(this.visible);
4311
+ });
4312
+ wrapper.appendChild(btn);
4313
+ return wrapper;
4314
+ }
4315
+ renderSeparated(length) {
4316
+ const pf = this.passwordField;
4317
+ const showToggle = !!pf.showToggle;
4318
+ const value = this.value ?? "";
4319
+ const valueString = String(value).slice(0, length);
4320
+ const container = document.createElement("div");
4321
+ container.className = "easy-form-password-separated";
4322
+ for (let i = 0; i < length; i++) {
4323
+ const input = document.createElement("input");
4324
+ input.type = "password";
4325
+ input.maxLength = 1;
4326
+ input.className = "easy-form-password-separated-input";
4327
+ input.setAttribute("aria-label", `Car\xE1cter ${i + 1} de ${length}`);
4328
+ if (valueString[i]) input.value = valueString[i];
4329
+ this.applyCommonProps(input);
4330
+ input.id = i === 0 ? this.getFieldId() : `${this.getFieldId()}-${i}`;
4331
+ if (i > 0) input.removeAttribute("name");
4332
+ const currentIndex = i;
4333
+ input.addEventListener("input", (e) => {
4334
+ const target = e.target;
4335
+ const val = target.value;
4336
+ if (val && currentIndex < length - 1) {
4337
+ this.inputs[currentIndex + 1].focus();
4338
+ }
4339
+ this.updateSeparatedValue();
4340
+ });
4341
+ input.addEventListener("keydown", (e) => {
4342
+ if (e.key === "Backspace" && !e.target.value && currentIndex > 0) {
4343
+ this.inputs[currentIndex - 1].focus();
4344
+ this.inputs[currentIndex - 1].value = "";
4345
+ this.updateSeparatedValue();
4346
+ }
4347
+ if (e.key === "ArrowLeft" && currentIndex > 0) {
4348
+ e.preventDefault();
4349
+ this.inputs[currentIndex - 1].focus();
4350
+ }
4351
+ if (e.key === "ArrowRight" && currentIndex < length - 1) {
4352
+ e.preventDefault();
4353
+ this.inputs[currentIndex + 1].focus();
4354
+ }
4355
+ });
4356
+ input.addEventListener("paste", (e) => {
4357
+ e.preventDefault();
4358
+ const text = (e.clipboardData || window.clipboardData)?.getData("text") || "";
4359
+ const chars = text.slice(0, length - currentIndex).split("");
4360
+ for (let j = 0; j < chars.length && currentIndex + j < length; j++) {
4361
+ this.inputs[currentIndex + j].value = chars[j];
4362
+ }
4363
+ const nextIdx = Math.min(currentIndex + chars.length, length - 1);
4364
+ this.inputs[nextIdx].focus();
4365
+ this.updateSeparatedValue();
4366
+ });
4367
+ input.addEventListener("focus", (e) => e.target.select());
4368
+ input.addEventListener("blur", () => this.onBlur());
4369
+ this.inputs.push(input);
4370
+ container.appendChild(input);
4371
+ }
4372
+ if (showToggle) {
4373
+ const toggleWrap = document.createElement("div");
4374
+ toggleWrap.className = "easy-form-password-separated-toggle";
4375
+ const btn = document.createElement("button");
4376
+ btn.type = "button";
4377
+ btn.className = "easy-form-password-toggle";
4378
+ btn.setAttribute("aria-label", "Mostrar contrase\xF1a");
4379
+ btn.innerHTML = eyeSvg(false);
4380
+ btn.addEventListener("click", () => {
4381
+ this.visible = !this.visible;
4382
+ const type = this.visible ? "text" : "password";
4383
+ this.inputs.forEach((inp) => {
4384
+ inp.type = type;
4385
+ });
4386
+ btn.setAttribute("aria-label", this.visible ? "Ocultar contrase\xF1a" : "Mostrar contrase\xF1a");
4387
+ btn.innerHTML = eyeSvg(this.visible);
4388
+ });
4389
+ toggleWrap.appendChild(btn);
4390
+ container.appendChild(toggleWrap);
4391
+ }
4392
+ return container;
4393
+ }
4394
+ updateSeparatedValue() {
4395
+ const value = this.inputs.map((inp) => inp.value).join("");
4396
+ this.onChange(value || null);
4397
+ }
4398
+ };
4399
+
4193
4400
  // src/components/inputs/index.ts
4194
4401
  function createInput(field, value, error, onChange, onBlur) {
4195
4402
  switch (field.type) {
4196
4403
  case "text":
4197
4404
  case "email":
4198
- case "password":
4199
4405
  return new TextInput(field, value, error, onChange, onBlur).render();
4406
+ case "password":
4407
+ return new PasswordInput(field, value, error, onChange, onBlur).render();
4200
4408
  case "number":
4201
4409
  return new NumberInput(field, value, error, onChange, onBlur).render();
4202
4410
  case "textarea":
@@ -5312,6 +5520,20 @@ var EasyForm = class extends BrowserHTMLElement {
5312
5520
  }
5313
5521
  return result;
5314
5522
  }
5523
+ /**
5524
+ * Aplica estado disabled/loading a un clone de slot: deshabilita elementos
5525
+ * interactivos y añade clase para estilos. Respeta el estado del formulario.
5526
+ */
5527
+ applySlotDisabledState(slotElement) {
5528
+ if (!this.disabled && !this.loading) return;
5529
+ slotElement.classList.add("easy-form-slot-disabled");
5530
+ const interactives = slotElement.querySelectorAll(
5531
+ "input, textarea, select, button"
5532
+ );
5533
+ interactives.forEach((el) => {
5534
+ if ("disabled" in el) el.disabled = true;
5535
+ });
5536
+ }
5315
5537
  /**
5316
5538
  * Renderiza campos normales
5317
5539
  */
@@ -5321,6 +5543,7 @@ var EasyForm = class extends BrowserHTMLElement {
5321
5543
  const endSlots2 = slotClones.get(-1);
5322
5544
  if (endSlots2 && endSlots2.length > 0) {
5323
5545
  for (const slotElement of endSlots2) {
5546
+ this.applySlotDisabledState(slotElement);
5324
5547
  container.appendChild(slotElement);
5325
5548
  }
5326
5549
  }
@@ -5332,6 +5555,7 @@ var EasyForm = class extends BrowserHTMLElement {
5332
5555
  const slotsForRow = slotClonesByRow.get(rowIndex);
5333
5556
  if (slotsForRow && slotsForRow.length > 0) {
5334
5557
  for (const slotElement of slotsForRow) {
5558
+ this.applySlotDisabledState(slotElement);
5335
5559
  container.appendChild(slotElement);
5336
5560
  }
5337
5561
  slotClonesByRow.delete(rowIndex);
@@ -5345,6 +5569,7 @@ var EasyForm = class extends BrowserHTMLElement {
5345
5569
  const endSlots = slotClonesByRow.get(-1);
5346
5570
  if (endSlots && endSlots.length > 0) {
5347
5571
  for (const slotElement of endSlots) {
5572
+ this.applySlotDisabledState(slotElement);
5348
5573
  container.appendChild(slotElement);
5349
5574
  }
5350
5575
  }