easy-forms-core 1.1.8 → 1.1.10

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/dist/easy-form.js CHANGED
@@ -59,6 +59,10 @@ var SchemaParser = class {
59
59
  "switch",
60
60
  "date",
61
61
  "file",
62
+ "file-drop",
63
+ "map",
64
+ "rating",
65
+ "slider",
62
66
  "array",
63
67
  "group",
64
68
  "row",
@@ -158,6 +162,22 @@ var SchemaParser = class {
158
162
  defaults.numeric = true;
159
163
  }
160
164
  break;
165
+ case "slider":
166
+ if (!("min" in field)) {
167
+ defaults.min = 0;
168
+ }
169
+ if (!("max" in field)) {
170
+ defaults.max = 100;
171
+ }
172
+ if (!("step" in field)) {
173
+ defaults.step = 1;
174
+ }
175
+ break;
176
+ case "rating":
177
+ if (!("max" in field)) {
178
+ defaults.max = 5;
179
+ }
180
+ break;
161
181
  }
162
182
  return { ...defaults, ...field };
163
183
  }
@@ -234,6 +254,140 @@ function getBaseStyles(colors) {
234
254
  font-weight: 500;
235
255
  color: var(--easy-form-label-color);
236
256
  }
257
+ .easy-form-label-down .easy-form-label {
258
+ margin-bottom: 0;
259
+ margin-top: 0.5rem;
260
+ }
261
+ .easy-form-field-inner-horizontal {
262
+ display: flex;
263
+ flex-direction: row;
264
+ align-items: center;
265
+ gap: 0.75rem;
266
+ flex-wrap: wrap;
267
+ }
268
+ .easy-form-field-inner-horizontal .easy-form-label {
269
+ margin-bottom: 0;
270
+ margin-top: 0;
271
+ flex-shrink: 0;
272
+ }
273
+ .easy-form-direction-vertical {
274
+ display: block;
275
+ }
276
+ .easy-form-direction-horizontal {
277
+ display: flex;
278
+ flex-direction: row;
279
+ flex-wrap: wrap;
280
+ gap: 1rem;
281
+ }
282
+ .easy-form-direction-horizontal > * {
283
+ flex: 1 1 auto;
284
+ }
285
+ .easy-form-completed-indicator {
286
+ margin-bottom: 1rem;
287
+ }
288
+ .easy-form-completed-track {
289
+ height: 6px;
290
+ background: var(--easy-form-border);
291
+ border-radius: 3px;
292
+ overflow: hidden;
293
+ }
294
+ .easy-form-completed-fill {
295
+ height: 100%;
296
+ background: var(--easy-form-primary);
297
+ border-radius: 3px;
298
+ transition: width 0.2s ease;
299
+ }
300
+ .easy-form-slider-wrapper {
301
+ display: flex;
302
+ align-items: center;
303
+ gap: 1rem;
304
+ }
305
+ .easy-form-slider-value {
306
+ min-width: 2rem;
307
+ font-weight: 500;
308
+ }
309
+ .easy-form-rating {
310
+ display: inline-flex;
311
+ gap: 0.25rem;
312
+ }
313
+ .easy-form-rating-stars {
314
+ display: flex;
315
+ gap: 0.25rem;
316
+ }
317
+ .easy-form-rating-star {
318
+ background: none;
319
+ border: none;
320
+ padding: 0;
321
+ cursor: pointer;
322
+ color: var(--easy-form-border);
323
+ }
324
+ .easy-form-rating-star svg {
325
+ display: block;
326
+ }
327
+ .easy-form-rating-star-filled {
328
+ color: #f5a623;
329
+ }
330
+ .easy-form-rating-star:hover,
331
+ .easy-form-rating-star:focus {
332
+ color: var(--easy-form-primary);
333
+ }
334
+ .easy-form-file-drop {
335
+ border: 2px dashed var(--easy-form-border);
336
+ border-radius: 8px;
337
+ padding: 2rem;
338
+ text-align: center;
339
+ cursor: pointer;
340
+ transition: border-color 0.2s, background 0.2s;
341
+ }
342
+ .easy-form-file-drop:hover,
343
+ .easy-form-file-drop-over {
344
+ border-color: var(--easy-form-primary);
345
+ background: rgba(0, 123, 255, 0.05);
346
+ }
347
+ .easy-form-file-drop-list {
348
+ margin-top: 0.75rem;
349
+ display: flex;
350
+ flex-direction: column;
351
+ gap: 0.25rem;
352
+ }
353
+ .easy-form-file-drop-item {
354
+ font-size: 0.875rem;
355
+ color: var(--easy-form-text);
356
+ padding: 0.25rem 0;
357
+ border-bottom: 1px solid var(--easy-form-border);
358
+ }
359
+ .easy-form-map-inputs {
360
+ display: flex;
361
+ gap: 1rem;
362
+ margin-bottom: 0.5rem;
363
+ }
364
+ .easy-form-map-inputs input {
365
+ flex: 1;
366
+ }
367
+ .easy-form-map-placeholder,
368
+ .easy-form-map-container {
369
+ min-height: 300px;
370
+ height: 300px;
371
+ border-radius: 4px;
372
+ position: relative;
373
+ overflow: hidden;
374
+ width: 100%;
375
+ display: block;
376
+ box-sizing: border-box;
377
+ }
378
+ .easy-form-map-container {
379
+ background: #f5f5f5;
380
+ }
381
+ .easy-form-map-container .leaflet-container {
382
+ height: 100% !important;
383
+ width: 100% !important;
384
+ position: relative !important;
385
+ }
386
+ .easy-form-map-container .leaflet-tile-container {
387
+ position: absolute;
388
+ left: 0;
389
+ top: 0;
390
+ }
237
391
  .easy-form-required {
238
392
  color: var(--easy-form-error);
239
393
  }
@@ -689,6 +843,62 @@ function getBaseStyles(colors) {
689
843
  .easy-form-otp-input:invalid {
690
844
  border-color: var(--easy-form-error);
691
845
  }
846
+ /* Password inner (input + toggle) */
847
+ .easy-form-password-inner {
848
+ display: flex;
849
+ align-items: center;
850
+ gap: 0.5rem;
851
+ }
852
+ .easy-form-password-inner .easy-form-password-input {
853
+ flex: 1;
854
+ min-width: 0;
855
+ }
856
+ .easy-form-password-toggle {
857
+ flex-shrink: 0;
858
+ display: inline-flex;
859
+ align-items: center;
860
+ justify-content: center;
861
+ padding: 0.35rem;
862
+ background: transparent;
863
+ border: 1px solid var(--easy-form-border);
864
+ border-radius: 4px;
865
+ cursor: pointer;
866
+ color: var(--easy-form-text);
867
+ }
868
+ .easy-form-password-toggle:hover {
869
+ border-color: var(--easy-form-primary);
870
+ color: var(--easy-form-primary);
871
+ }
872
+ .easy-form-password-toggle:focus {
873
+ outline: none;
874
+ box-shadow: 0 0 0 2px var(--easy-form-primary);
875
+ }
876
+ /* Password car\xE1cter separado */
877
+ .easy-form-password-separated {
878
+ display: flex;
879
+ flex-wrap: nowrap;
880
+ gap: 0.5rem;
881
+ align-items: center;
882
+ }
883
+ .easy-form-password-separated-input {
884
+ width: 2.5rem;
885
+ height: 2.5rem;
886
+ text-align: center;
887
+ font-size: 1.25rem;
888
+ border: 2px solid var(--easy-form-border);
889
+ border-radius: 4px;
890
+ transition: border-color 0.2s ease;
891
+ }
892
+ .easy-form-password-separated-input:focus {
893
+ border-color: var(--easy-form-primary);
894
+ outline: none;
895
+ }
896
+ .easy-form-password-separated-toggle {
897
+ flex-shrink: 0;
898
+ }
899
+ .easy-form-password-separated-toggle .easy-form-password-toggle {
900
+ padding: 0.35rem;
901
+ }
692
902
  /* Loading Overlay (sobre el formulario) */
693
903
  .easy-form-loading-overlay {
694
904
  position: absolute;
@@ -2842,25 +3052,54 @@ var BaseInput = class {
2842
3052
  const componentContainerClasses = inputClasses.filter(
2843
3053
  (cls) => cls.startsWith("easy-form-") && cls !== "easy-form-field" && (cls.includes("-container") || cls.includes("-wrapper"))
2844
3054
  );
3055
+ const labelPosition = this.field.labelPosition ?? "up";
2845
3056
  if (componentContainerClasses.length > 0) {
2846
- container.className = `easy-form-field ${componentContainerClasses.join(" ")}`;
3057
+ container.className = `easy-form-field easy-form-label-${labelPosition} ${componentContainerClasses.join(" ")}`;
2847
3058
  } else {
2848
- container.className = "easy-form-field";
3059
+ container.className = `easy-form-field easy-form-label-${labelPosition}`;
2849
3060
  }
2850
- if (this.field.label) {
2851
- const label = document.createElement("label");
2852
- label.className = "easy-form-label";
2853
- label.setAttribute("for", this.getFieldId());
2854
- label.textContent = this.field.label;
3061
+ if (labelPosition === "none" && this.field.label) {
3062
+ const el = input.querySelector("input, textarea, select") || input;
3063
+ if (el instanceof HTMLElement) {
3064
+ el.setAttribute("aria-label", this.field.label);
3065
+ }
3066
+ }
3067
+ const createLabel = () => {
3068
+ if (!this.field.label || labelPosition === "none") return null;
3069
+ const label2 = document.createElement("label");
3070
+ label2.className = "easy-form-label";
3071
+ label2.setAttribute("for", this.getFieldId());
3072
+ label2.textContent = this.field.label;
2855
3073
  if (this.field.validations?.some((v) => v.type === "required")) {
2856
3074
  const required = document.createElement("span");
2857
3075
  required.className = "easy-form-required";
2858
3076
  required.textContent = " *";
2859
- label.appendChild(required);
3077
+ label2.appendChild(required);
2860
3078
  }
2861
- container.appendChild(label);
3079
+ return label2;
3080
+ };
3081
+ const label = createLabel();
3082
+ if (labelPosition === "up") {
3083
+ if (label) container.appendChild(label);
3084
+ container.appendChild(input);
3085
+ } else if (labelPosition === "down") {
3086
+ container.appendChild(input);
3087
+ if (label) container.appendChild(label);
3088
+ } else if (labelPosition === "left") {
3089
+ const wrapper = document.createElement("div");
3090
+ wrapper.className = "easy-form-field-inner easy-form-field-inner-horizontal";
3091
+ if (label) wrapper.appendChild(label);
3092
+ wrapper.appendChild(input);
3093
+ container.appendChild(wrapper);
3094
+ } else if (labelPosition === "right") {
3095
+ const wrapper = document.createElement("div");
3096
+ wrapper.className = "easy-form-field-inner easy-form-field-inner-horizontal";
3097
+ wrapper.appendChild(input);
3098
+ if (label) wrapper.appendChild(label);
3099
+ container.appendChild(wrapper);
3100
+ } else {
3101
+ container.appendChild(input);
2862
3102
  }
2863
- container.appendChild(input);
2864
3103
  if (this.field.description) {
2865
3104
  const description = document.createElement("p");
2866
3105
  description.className = "easy-form-description";
@@ -4196,13 +4435,508 @@ var OTPInput = class extends BaseInput {
4196
4435
  }
4197
4436
  };
4198
4437
 
4438
+ // src/components/inputs/password-input.ts
4439
+ var DEFAULT_SEPARATED_LENGTH = 6;
4440
+ function eyeSvg(visible) {
4441
+ if (visible) {
4442
+ 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>`;
4443
+ }
4444
+ 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>`;
4445
+ }
4446
+ var PasswordInput = class extends BaseInput {
4447
+ constructor() {
4448
+ super(...arguments);
4449
+ this.inputs = [];
4450
+ this.visible = false;
4451
+ }
4452
+ get passwordField() {
4453
+ return this.field;
4454
+ }
4455
+ render() {
4456
+ const pf = this.passwordField;
4457
+ const useSeparated = !!pf.characterSeparated;
4458
+ const length = useSeparated ? typeof pf.characterSeparated === "number" ? pf.characterSeparated : DEFAULT_SEPARATED_LENGTH : 0;
4459
+ if (useSeparated) {
4460
+ return this.createFieldContainer(this.renderSeparated(length));
4461
+ }
4462
+ return this.createFieldContainer(this.renderSingle());
4463
+ }
4464
+ renderSingle() {
4465
+ const pf = this.passwordField;
4466
+ const showToggle = !!pf.showToggle;
4467
+ const input = document.createElement("input");
4468
+ input.type = "password";
4469
+ input.value = this.value ?? "";
4470
+ input.id = this.getFieldId();
4471
+ this.applyCommonProps(input);
4472
+ if (this.field.placeholder) input.placeholder = this.field.placeholder;
4473
+ input.addEventListener("input", (e) => {
4474
+ this.onChange(e.target.value);
4475
+ });
4476
+ input.addEventListener("blur", () => this.onBlur());
4477
+ if (!showToggle) {
4478
+ return input;
4479
+ }
4480
+ const wrapper = document.createElement("div");
4481
+ wrapper.className = "easy-form-password-inner";
4482
+ input.classList.add("easy-form-password-input");
4483
+ wrapper.appendChild(input);
4484
+ const btn = document.createElement("button");
4485
+ btn.type = "button";
4486
+ btn.className = "easy-form-password-toggle";
4487
+ btn.setAttribute("aria-label", "Mostrar contrase\xF1a");
4488
+ btn.innerHTML = eyeSvg(false);
4489
+ btn.addEventListener("click", () => {
4490
+ this.visible = !this.visible;
4491
+ input.type = this.visible ? "text" : "password";
4492
+ btn.setAttribute("aria-label", this.visible ? "Ocultar contrase\xF1a" : "Mostrar contrase\xF1a");
4493
+ btn.innerHTML = eyeSvg(this.visible);
4494
+ });
4495
+ wrapper.appendChild(btn);
4496
+ return wrapper;
4497
+ }
4498
+ renderSeparated(length) {
4499
+ const pf = this.passwordField;
4500
+ const showToggle = !!pf.showToggle;
4501
+ const value = this.value ?? "";
4502
+ const valueString = String(value).slice(0, length);
4503
+ const container = document.createElement("div");
4504
+ container.className = "easy-form-password-separated";
4505
+ for (let i = 0; i < length; i++) {
4506
+ const input = document.createElement("input");
4507
+ input.type = "password";
4508
+ input.maxLength = 1;
4509
+ input.className = "easy-form-password-separated-input";
4510
+ input.setAttribute("aria-label", `Car\xE1cter ${i + 1} de ${length}`);
4511
+ if (valueString[i]) input.value = valueString[i];
4512
+ this.applyCommonProps(input);
4513
+ input.id = i === 0 ? this.getFieldId() : `${this.getFieldId()}-${i}`;
4514
+ if (i > 0) input.removeAttribute("name");
4515
+ const currentIndex = i;
4516
+ input.addEventListener("input", (e) => {
4517
+ const target = e.target;
4518
+ const val = target.value;
4519
+ if (val && currentIndex < length - 1) {
4520
+ this.inputs[currentIndex + 1].focus();
4521
+ }
4522
+ this.updateSeparatedValue();
4523
+ });
4524
+ input.addEventListener("keydown", (e) => {
4525
+ if (e.key === "Backspace" && !e.target.value && currentIndex > 0) {
4526
+ this.inputs[currentIndex - 1].focus();
4527
+ this.inputs[currentIndex - 1].value = "";
4528
+ this.updateSeparatedValue();
4529
+ }
4530
+ if (e.key === "ArrowLeft" && currentIndex > 0) {
4531
+ e.preventDefault();
4532
+ this.inputs[currentIndex - 1].focus();
4533
+ }
4534
+ if (e.key === "ArrowRight" && currentIndex < length - 1) {
4535
+ e.preventDefault();
4536
+ this.inputs[currentIndex + 1].focus();
4537
+ }
4538
+ });
4539
+ input.addEventListener("paste", (e) => {
4540
+ e.preventDefault();
4541
+ const text = (e.clipboardData || window.clipboardData)?.getData("text") || "";
4542
+ const chars = text.slice(0, length - currentIndex).split("");
4543
+ for (let j = 0; j < chars.length && currentIndex + j < length; j++) {
4544
+ this.inputs[currentIndex + j].value = chars[j];
4545
+ }
4546
+ const nextIdx = Math.min(currentIndex + chars.length, length - 1);
4547
+ this.inputs[nextIdx].focus();
4548
+ this.updateSeparatedValue();
4549
+ });
4550
+ input.addEventListener("focus", (e) => e.target.select());
4551
+ input.addEventListener("blur", () => this.onBlur());
4552
+ this.inputs.push(input);
4553
+ container.appendChild(input);
4554
+ }
4555
+ if (showToggle) {
4556
+ const toggleWrap = document.createElement("div");
4557
+ toggleWrap.className = "easy-form-password-separated-toggle";
4558
+ const btn = document.createElement("button");
4559
+ btn.type = "button";
4560
+ btn.className = "easy-form-password-toggle";
4561
+ btn.setAttribute("aria-label", "Mostrar contrase\xF1a");
4562
+ btn.innerHTML = eyeSvg(false);
4563
+ btn.addEventListener("click", () => {
4564
+ this.visible = !this.visible;
4565
+ const type = this.visible ? "text" : "password";
4566
+ this.inputs.forEach((inp) => {
4567
+ inp.type = type;
4568
+ });
4569
+ btn.setAttribute("aria-label", this.visible ? "Ocultar contrase\xF1a" : "Mostrar contrase\xF1a");
4570
+ btn.innerHTML = eyeSvg(this.visible);
4571
+ });
4572
+ toggleWrap.appendChild(btn);
4573
+ container.appendChild(toggleWrap);
4574
+ }
4575
+ return container;
4576
+ }
4577
+ updateSeparatedValue() {
4578
+ const value = this.inputs.map((inp) => inp.value).join("");
4579
+ this.onChange(value || null);
4580
+ }
4581
+ };
4582
+
4583
+ // src/components/inputs/file-drop-input.ts
4584
+ var FileDropInput = class extends BaseInput {
4585
+ render() {
4586
+ const fileDropField = this.field;
4587
+ const accept = fileDropField.accept ?? "";
4588
+ const multiple = fileDropField.multiple ?? false;
4589
+ const dropZone = document.createElement("div");
4590
+ dropZone.className = "easy-form-file-drop";
4591
+ dropZone.setAttribute("tabindex", "0");
4592
+ const hiddenInput = document.createElement("input");
4593
+ hiddenInput.type = "file";
4594
+ hiddenInput.className = "easy-form-file-drop-input";
4595
+ hiddenInput.style.display = "none";
4596
+ if (accept) hiddenInput.accept = accept;
4597
+ if (multiple) hiddenInput.multiple = true;
4598
+ const label = document.createElement("span");
4599
+ label.className = "easy-form-file-drop-label";
4600
+ label.textContent = "Arrastra archivos aqu\xED o haz clic para seleccionar";
4601
+ dropZone.appendChild(label);
4602
+ dropZone.appendChild(hiddenInput);
4603
+ const fileList = document.createElement("div");
4604
+ fileList.className = "easy-form-file-drop-list";
4605
+ const renderFileList = (files) => {
4606
+ const names = !files ? [] : Array.isArray(files) ? files.map((f) => f.name) : [files.name];
4607
+ fileList.innerHTML = "";
4608
+ if (names.length === 0) {
4609
+ fileList.style.display = "none";
4610
+ return;
4611
+ }
4612
+ fileList.style.display = "block";
4613
+ names.forEach((name) => {
4614
+ const item = document.createElement("div");
4615
+ item.className = "easy-form-file-drop-item";
4616
+ item.textContent = name;
4617
+ fileList.appendChild(item);
4618
+ });
4619
+ };
4620
+ renderFileList(this.value);
4621
+ const handleFiles = (files) => {
4622
+ if (!files || files.length === 0) return;
4623
+ const arr = multiple ? Array.from(files) : files[0];
4624
+ this.onChange(arr);
4625
+ this.onBlur();
4626
+ renderFileList(arr);
4627
+ };
4628
+ dropZone.addEventListener("click", () => {
4629
+ hiddenInput.click();
4630
+ });
4631
+ hiddenInput.addEventListener("change", (e) => {
4632
+ const target = e.target;
4633
+ handleFiles(target.files);
4634
+ target.value = "";
4635
+ });
4636
+ dropZone.addEventListener("dragover", (e) => {
4637
+ e.preventDefault();
4638
+ e.stopPropagation();
4639
+ dropZone.classList.add("easy-form-file-drop-over");
4640
+ });
4641
+ dropZone.addEventListener("dragleave", (e) => {
4642
+ e.preventDefault();
4643
+ e.stopPropagation();
4644
+ dropZone.classList.remove("easy-form-file-drop-over");
4645
+ });
4646
+ dropZone.addEventListener("drop", (e) => {
4647
+ e.preventDefault();
4648
+ e.stopPropagation();
4649
+ dropZone.classList.remove("easy-form-file-drop-over");
4650
+ handleFiles(e.dataTransfer?.files ?? null);
4651
+ });
4652
+ dropZone.appendChild(fileList);
4653
+ return this.createFieldContainer(dropZone);
4654
+ }
4655
+ };
4656
+
4657
+ // src/components/inputs/map-input.ts
4658
+ var LEAFLET_CSS = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
4659
+ var MapInput = class extends BaseInput {
4660
+ constructor() {
4661
+ super(...arguments);
4662
+ this.mapInstance = null;
4663
+ this.markerInstance = null;
4664
+ }
4665
+ render() {
4666
+ const mapField = this.field;
4667
+ const center = mapField.center ?? { lat: 0, lng: 0 };
4668
+ const zoom = mapField.zoom ?? 13;
4669
+ const value = this.value;
4670
+ const lat = typeof value === "object" && value?.lat != null ? value.lat : center.lat;
4671
+ const lng = typeof value === "object" && value?.lng != null ? value.lng : center.lng;
4672
+ const container = document.createElement("div");
4673
+ container.className = "easy-form-map";
4674
+ const inputsWrapper = document.createElement("div");
4675
+ inputsWrapper.className = "easy-form-map-inputs";
4676
+ const latInput = document.createElement("input");
4677
+ latInput.type = "number";
4678
+ latInput.step = "any";
4679
+ latInput.placeholder = "Latitud";
4680
+ latInput.value = String(lat);
4681
+ latInput.className = "easy-form-map-lat";
4682
+ const lngInput = document.createElement("input");
4683
+ lngInput.type = "number";
4684
+ lngInput.step = "any";
4685
+ lngInput.placeholder = "Longitud";
4686
+ lngInput.value = String(lng);
4687
+ lngInput.className = "easy-form-map-lng";
4688
+ const updateFromInputs = () => {
4689
+ const latVal = parseFloat(latInput.value);
4690
+ const lngVal = parseFloat(lngInput.value);
4691
+ if (!isNaN(latVal) && !isNaN(lngVal)) {
4692
+ this.onChange({ lat: latVal, lng: lngVal });
4693
+ this.syncMarkerPosition(latVal, lngVal);
4694
+ }
4695
+ this.onBlur();
4696
+ };
4697
+ latInput.addEventListener("change", updateFromInputs);
4698
+ lngInput.addEventListener("change", updateFromInputs);
4699
+ inputsWrapper.appendChild(latInput);
4700
+ inputsWrapper.appendChild(lngInput);
4701
+ container.appendChild(inputsWrapper);
4702
+ const mapContainer = document.createElement("div");
4703
+ mapContainer.className = "easy-form-map-container";
4704
+ container.appendChild(mapContainer);
4705
+ const linkId = "easy-form-leaflet-css";
4706
+ let cssLoaded = false;
4707
+ const link = document.createElement("link");
4708
+ link.id = linkId;
4709
+ link.rel = "stylesheet";
4710
+ link.href = LEAFLET_CSS;
4711
+ link.onload = () => {
4712
+ cssLoaded = true;
4713
+ };
4714
+ container.insertBefore(link, container.firstChild);
4715
+ const initMap = async () => {
4716
+ if (typeof window === "undefined") return;
4717
+ mapContainer.style.height = "300px";
4718
+ mapContainer.style.width = "100%";
4719
+ mapContainer.style.position = "relative";
4720
+ mapContainer.style.overflow = "hidden";
4721
+ mapContainer.style.display = "block";
4722
+ await new Promise((resolve) => {
4723
+ const checkReady = () => {
4724
+ if (cssLoaded && mapContainer.offsetWidth > 0 && mapContainer.offsetHeight > 0) {
4725
+ resolve(void 0);
4726
+ } else {
4727
+ requestAnimationFrame(checkReady);
4728
+ }
4729
+ };
4730
+ checkReady();
4731
+ });
4732
+ await new Promise((resolve) => setTimeout(resolve, 50));
4733
+ let L;
4734
+ try {
4735
+ L = await import("leaflet");
4736
+ } catch {
4737
+ mapContainer.innerHTML = `
4738
+ <div style="padding:2rem;text-align:center;color:#666;font-size:0.875rem;background:#f9f9f9;border-radius:4px;">
4739
+ <strong>Campo map requiere Leaflet</strong><br><br>
4740
+ Instala e importa Leaflet para usar el mapa interactivo:<br>
4741
+ <code>npm install leaflet</code><br><br>
4742
+ En tu app: <code>import 'leaflet'</code> y <code>import 'leaflet/dist/leaflet.css'</code><br>
4743
+ <a href="https://easyforms.dev/docs/tipos-campos#map" target="_blank" rel="noopener" style="color:var(--easy-form-primary,#007bff);">Ver documentaci\xF3n</a>
4744
+ </div>
4745
+ `;
4746
+ return;
4747
+ }
4748
+ delete L.Icon.Default.prototype._getIconUrl;
4749
+ L.Icon.Default.mergeOptions({
4750
+ iconRetinaUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png",
4751
+ iconUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png",
4752
+ shadowUrl: "https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png"
4753
+ });
4754
+ this.mapInstance = L.map(mapContainer, {
4755
+ preferCanvas: false,
4756
+ zoomControl: true
4757
+ }).setView([lat, lng], zoom);
4758
+ const tileLayer = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
4759
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
4760
+ maxZoom: 19
4761
+ }).addTo(this.mapInstance);
4762
+ this.mapInstance.invalidateSize();
4763
+ tileLayer.on("load", () => {
4764
+ if (this.mapInstance) {
4765
+ this.mapInstance.invalidateSize();
4766
+ }
4767
+ });
4768
+ const invalidateSize = () => {
4769
+ if (this.mapInstance) {
4770
+ this.mapInstance.invalidateSize();
4771
+ }
4772
+ };
4773
+ setTimeout(invalidateSize, 100);
4774
+ setTimeout(invalidateSize, 300);
4775
+ setTimeout(invalidateSize, 500);
4776
+ const observer = new IntersectionObserver((entries) => {
4777
+ entries.forEach((entry) => {
4778
+ if (entry.isIntersecting && this.mapInstance) {
4779
+ setTimeout(() => {
4780
+ this.mapInstance?.invalidateSize();
4781
+ }, 100);
4782
+ }
4783
+ });
4784
+ });
4785
+ observer.observe(mapContainer);
4786
+ this.markerInstance = L.marker([lat, lng], { draggable: true }).addTo(this.mapInstance).on("dragend", () => {
4787
+ const pos = this.markerInstance.getLatLng();
4788
+ latInput.value = String(Number(pos.lat.toFixed(6)));
4789
+ lngInput.value = String(Number(pos.lng.toFixed(6)));
4790
+ this.onChange({ lat: pos.lat, lng: pos.lng });
4791
+ this.onBlur();
4792
+ });
4793
+ this.mapInstance.on("click", (e) => {
4794
+ const { lat: newLat, lng: newLng } = e.latlng;
4795
+ this.markerInstance.setLatLng([newLat, newLng]);
4796
+ latInput.value = String(Number(newLat.toFixed(6)));
4797
+ lngInput.value = String(Number(newLng.toFixed(6)));
4798
+ this.onChange({ lat: newLat, lng: newLng });
4799
+ this.onBlur();
4800
+ });
4801
+ };
4802
+ initMap();
4803
+ return this.createFieldContainer(container);
4804
+ }
4805
+ syncMarkerPosition(newLat, newLng) {
4806
+ if (this.mapInstance && this.markerInstance) {
4807
+ this.markerInstance.setLatLng([newLat, newLng]);
4808
+ this.mapInstance.setView([newLat, newLng]);
4809
+ setTimeout(() => {
4810
+ this.mapInstance?.invalidateSize();
4811
+ }, 50);
4812
+ }
4813
+ }
4814
+ };
4815
+
4816
+ // src/components/inputs/rating-input.ts
4817
+ var STAR_SVG = (filled) => `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="${filled ? "currentColor" : "none"}" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" role="img" aria-hidden="true"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`;
4818
+ var RatingInput = class extends BaseInput {
4819
+ render() {
4820
+ const ratingField = this.field;
4821
+ const max = ratingField.max ?? 5;
4822
+ const half = ratingField.half ?? false;
4823
+ const currentValue = this.value != null ? Number(this.value) : 0;
4824
+ const container = document.createElement("div");
4825
+ container.className = "easy-form-rating";
4826
+ container.setAttribute("role", "slider");
4827
+ container.setAttribute("aria-valuemin", "0");
4828
+ container.setAttribute("aria-valuemax", String(max));
4829
+ container.setAttribute("aria-valuenow", String(currentValue));
4830
+ container.setAttribute("aria-label", this.field.label || "Rating");
4831
+ container.setAttribute("tabindex", "0");
4832
+ const starsContainer = document.createElement("div");
4833
+ starsContainer.className = "easy-form-rating-stars";
4834
+ for (let i = 1; i <= max; i++) {
4835
+ const starWrapper = document.createElement("button");
4836
+ starWrapper.type = "button";
4837
+ const filled = currentValue >= i || half && currentValue >= i - 0.5;
4838
+ starWrapper.className = filled ? "easy-form-rating-star easy-form-rating-star-filled" : "easy-form-rating-star";
4839
+ starWrapper.innerHTML = STAR_SVG(filled);
4840
+ starWrapper.setAttribute("aria-label", `${i} de ${max}`);
4841
+ starWrapper.addEventListener("click", (e) => {
4842
+ e.preventDefault();
4843
+ const rect = e.target.closest("button")?.getBoundingClientRect();
4844
+ let val = i;
4845
+ if (half && rect) {
4846
+ const mid = rect.left + rect.width / 2;
4847
+ val = e.clientX < mid ? i - 0.5 : i;
4848
+ }
4849
+ this.onChange(val);
4850
+ this.onBlur();
4851
+ this.rerenderStars(container, max, val, half);
4852
+ container.setAttribute("aria-valuenow", String(val));
4853
+ });
4854
+ starsContainer.appendChild(starWrapper);
4855
+ }
4856
+ container.appendChild(starsContainer);
4857
+ container.addEventListener("keydown", (e) => {
4858
+ const cur = Number(container.getAttribute("aria-valuenow")) || 0;
4859
+ let newVal = cur;
4860
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
4861
+ e.preventDefault();
4862
+ newVal = Math.min(max, half ? cur + 0.5 : cur + 1);
4863
+ this.onChange(newVal);
4864
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
4865
+ e.preventDefault();
4866
+ newVal = Math.max(0, half ? cur - 0.5 : cur - 1);
4867
+ this.onChange(newVal);
4868
+ }
4869
+ if (newVal !== cur) {
4870
+ this.rerenderStars(container, max, newVal, half);
4871
+ container.setAttribute("aria-valuenow", String(newVal));
4872
+ }
4873
+ });
4874
+ return this.createFieldContainer(container);
4875
+ }
4876
+ rerenderStars(container, max, value, half) {
4877
+ const stars = container.querySelectorAll(".easy-form-rating-star");
4878
+ stars.forEach((star, i) => {
4879
+ const idx = i + 1;
4880
+ const filled = value >= idx || half && value >= idx - 0.5;
4881
+ star.className = filled ? "easy-form-rating-star easy-form-rating-star-filled" : "easy-form-rating-star";
4882
+ star.innerHTML = STAR_SVG(filled);
4883
+ });
4884
+ }
4885
+ };
4886
+
4887
+ // src/components/inputs/slider-input.ts
4888
+ var SliderInput = class extends BaseInput {
4889
+ render() {
4890
+ const sliderField = this.field;
4891
+ const min = sliderField.min ?? 0;
4892
+ const max = sliderField.max ?? 100;
4893
+ const step = sliderField.step ?? 1;
4894
+ const showValue = sliderField.showValue ?? false;
4895
+ const input = document.createElement("input");
4896
+ input.type = "range";
4897
+ input.min = String(min);
4898
+ input.max = String(max);
4899
+ input.step = String(step);
4900
+ input.value = this.value != null ? String(this.value) : String(min);
4901
+ input.setAttribute("aria-valuemin", String(min));
4902
+ input.setAttribute("aria-valuemax", String(max));
4903
+ input.setAttribute("aria-valuenow", input.value);
4904
+ this.applyCommonProps(input);
4905
+ input.addEventListener("input", (e) => {
4906
+ const target = e.target;
4907
+ const val = step >= 1 ? parseInt(target.value, 10) : parseFloat(target.value);
4908
+ this.onChange(isNaN(val) ? min : val);
4909
+ if (valueSpan) {
4910
+ valueSpan.textContent = target.value;
4911
+ }
4912
+ input.setAttribute("aria-valuenow", target.value);
4913
+ });
4914
+ input.addEventListener("change", () => {
4915
+ this.onBlur();
4916
+ });
4917
+ let valueSpan = null;
4918
+ if (showValue) {
4919
+ valueSpan = document.createElement("span");
4920
+ valueSpan.className = "easy-form-slider-value";
4921
+ valueSpan.textContent = input.value;
4922
+ const wrapper = document.createElement("div");
4923
+ wrapper.className = "easy-form-slider-wrapper";
4924
+ wrapper.appendChild(input);
4925
+ wrapper.appendChild(valueSpan);
4926
+ return this.createFieldContainer(wrapper);
4927
+ }
4928
+ return this.createFieldContainer(input);
4929
+ }
4930
+ };
4931
+
4199
4932
  // src/components/inputs/index.ts
4200
4933
  function createInput(field, value, error, onChange, onBlur) {
4201
4934
  switch (field.type) {
4202
4935
  case "text":
4203
4936
  case "email":
4204
- case "password":
4205
4937
  return new TextInput(field, value, error, onChange, onBlur).render();
4938
+ case "password":
4939
+ return new PasswordInput(field, value, error, onChange, onBlur).render();
4206
4940
  case "number":
4207
4941
  return new NumberInput(field, value, error, onChange, onBlur).render();
4208
4942
  case "textarea":
@@ -4219,6 +4953,14 @@ function createInput(field, value, error, onChange, onBlur) {
4219
4953
  return new DateInput(field, value, error, onChange, onBlur).render();
4220
4954
  case "file":
4221
4955
  return new FileInput(field, value, error, onChange, onBlur).render();
4956
+ case "file-drop":
4957
+ return new FileDropInput(field, value, error, onChange, onBlur).render();
4958
+ case "map":
4959
+ return new MapInput(field, value, error, onChange, onBlur).render();
4960
+ case "rating":
4961
+ return new RatingInput(field, value, error, onChange, onBlur).render();
4962
+ case "slider":
4963
+ return new SliderInput(field, value, error, onChange, onBlur).render();
4222
4964
  case "quantity":
4223
4965
  return new QuantityInput(field, value, error, onChange, onBlur).render();
4224
4966
  case "accordion-select":
@@ -4791,7 +5533,10 @@ var EasyForm = class extends BrowserHTMLElement {
4791
5533
  "max-attempts",
4792
5534
  "block-duration-minutes",
4793
5535
  "attempts-storage-key",
4794
- "submit-button"
5536
+ "submit-button",
5537
+ "label-position",
5538
+ "show-completed-indicator",
5539
+ "form-direction"
4795
5540
  ];
4796
5541
  }
4797
5542
  /**
@@ -5116,6 +5861,12 @@ var EasyForm = class extends BrowserHTMLElement {
5116
5861
  if (this.disabled || this.loading) {
5117
5862
  newFormElement.classList.add("easy-form-disabled");
5118
5863
  }
5864
+ const directionAttr = this.getAttribute("form-direction");
5865
+ const direction = schema.direction ?? (directionAttr === "vertical" || directionAttr === "horizontal" ? directionAttr : "vertical");
5866
+ newFormElement.classList.add(`easy-form-direction-${direction}`);
5867
+ const showCompletedAttr = this.getAttribute("show-completed-indicator");
5868
+ const completedIndicator = schema.completedIndicator ?? (showCompletedAttr !== null && showCompletedAttr !== "false");
5869
+ const completedPosition = typeof completedIndicator === "object" && completedIndicator?.position ? completedIndicator.position : "top";
5119
5870
  if (finalWizardState) {
5120
5871
  this.renderWizard(newFormElement, schema);
5121
5872
  } else {
@@ -5137,6 +5888,33 @@ var EasyForm = class extends BrowserHTMLElement {
5137
5888
  newFormElement.appendChild(submitWrapper);
5138
5889
  }
5139
5890
  }
5891
+ let formWrapper = null;
5892
+ if (completedIndicator) {
5893
+ const { completed, total } = this.getCompletedRequiredProgress(schema);
5894
+ const progressBar = document.createElement("div");
5895
+ progressBar.className = "easy-form-completed-indicator";
5896
+ progressBar.setAttribute("role", "progressbar");
5897
+ progressBar.setAttribute("aria-valuenow", String(completed));
5898
+ progressBar.setAttribute("aria-valuemin", "0");
5899
+ progressBar.setAttribute("aria-valuemax", String(total));
5900
+ progressBar.setAttribute("aria-label", `Campos obligatorios completados: ${completed} de ${total}`);
5901
+ const track = document.createElement("div");
5902
+ track.className = "easy-form-completed-track";
5903
+ const fill = document.createElement("div");
5904
+ fill.className = "easy-form-completed-fill";
5905
+ fill.style.width = total > 0 ? `${completed / total * 100}%` : "0%";
5906
+ track.appendChild(fill);
5907
+ progressBar.appendChild(track);
5908
+ formWrapper = document.createElement("div");
5909
+ formWrapper.className = "easy-form-wrapper";
5910
+ if (completedPosition === "top") {
5911
+ formWrapper.appendChild(progressBar);
5912
+ formWrapper.appendChild(newFormElement);
5913
+ } else {
5914
+ formWrapper.appendChild(newFormElement);
5915
+ formWrapper.appendChild(progressBar);
5916
+ }
5917
+ }
5140
5918
  const oldForm = this.shadow.querySelector("form");
5141
5919
  if (oldForm && oldForm.parentNode === this.shadow && oldForm !== newFormElement) {
5142
5920
  try {
@@ -5145,7 +5923,9 @@ var EasyForm = class extends BrowserHTMLElement {
5145
5923
  console.warn("Error al eliminar formulario anterior:", e);
5146
5924
  }
5147
5925
  }
5148
- this.shadow.appendChild(newFormElement);
5926
+ const oldWrapper = this.shadow.querySelector(".easy-form-wrapper");
5927
+ if (oldWrapper) oldWrapper.remove();
5928
+ this.shadow.appendChild(formWrapper || newFormElement);
5149
5929
  if (this.loading) {
5150
5930
  this.updateLoadingOverlay(newFormElement);
5151
5931
  }
@@ -5428,9 +6208,13 @@ var EasyForm = class extends BrowserHTMLElement {
5428
6208
  const errors = this.stateManager.getErrors(field.name);
5429
6209
  const error = errors.length > 0 ? errors[0] : void 0;
5430
6210
  const isFormDisabled = this.disabled || this.loading;
6211
+ const labelPositionAttr = this.getAttribute("label-position");
6212
+ const validLabelPositions = ["up", "down", "left", "right", "none"];
6213
+ const effectiveLabelPosition = field.labelPosition ?? (labelPositionAttr && validLabelPositions.includes(labelPositionAttr) ? labelPositionAttr : "up");
5431
6214
  const fieldWithDependencies = {
5432
6215
  ...field,
5433
- disabled: isFormDisabled || !isEnabled || field.disabled
6216
+ disabled: isFormDisabled || !isEnabled || field.disabled,
6217
+ labelPosition: effectiveLabelPosition
5434
6218
  };
5435
6219
  const customComponent = getCustomComponent(field.type);
5436
6220
  if (customComponent) {
@@ -5469,7 +6253,9 @@ var EasyForm = class extends BrowserHTMLElement {
5469
6253
  */
5470
6254
  renderGroup(field) {
5471
6255
  const groupContainer = document.createElement("div");
5472
- groupContainer.className = "easy-form-group";
6256
+ const groupField = field;
6257
+ const dir = groupField.direction ?? "vertical";
6258
+ groupContainer.className = `easy-form-group easy-form-direction-${dir}`;
5473
6259
  if (field.label) {
5474
6260
  const label = document.createElement("h3");
5475
6261
  label.className = "easy-form-group-label";
@@ -5600,6 +6386,49 @@ var EasyForm = class extends BrowserHTMLElement {
5600
6386
  onBlur: () => this.handleFieldBlur(field.name)
5601
6387
  });
5602
6388
  }
6389
+ /**
6390
+ * Obtiene el progreso de campos obligatorios completados
6391
+ */
6392
+ getCompletedRequiredProgress(schema) {
6393
+ const extractFields = (fields2) => {
6394
+ const result = [];
6395
+ for (const f of fields2) {
6396
+ if (f.type === "array" && "itemSchema" in f && f.itemSchema?.fields) {
6397
+ const items = this.stateManager.getValue(f.name);
6398
+ const count = Array.isArray(items) ? items.length : 0;
6399
+ for (let i = 0; i < count; i++) {
6400
+ for (const sf of f.itemSchema.fields) {
6401
+ result.push({ ...sf, name: `${f.name}.${i}.${sf.name}` });
6402
+ }
6403
+ }
6404
+ } else if ((f.type === "group" || f.type === "row") && "fields" in f && f.fields) {
6405
+ result.push(...extractFields(f.fields));
6406
+ } else {
6407
+ result.push(f);
6408
+ }
6409
+ }
6410
+ return result;
6411
+ };
6412
+ const fields = schema.steps ? this.stateManager.getCurrentStepFields() || [] : schema.fields || [];
6413
+ const allFields = extractFields(fields);
6414
+ const requiredFields = allFields.filter(
6415
+ (f) => f.validations?.some((v) => v.type === "required")
6416
+ );
6417
+ const visibleRequired = requiredFields.filter(
6418
+ (f) => this.stateManager.getFieldVisibility(f.name)
6419
+ );
6420
+ const total = visibleRequired.length;
6421
+ const completed = visibleRequired.filter((f) => {
6422
+ const errors = this.stateManager.getErrors(f.name);
6423
+ if (errors.length > 0) return false;
6424
+ const val = this.stateManager.getValue(f.name);
6425
+ if (val === null || val === void 0) return false;
6426
+ if (typeof val === "string" && val.trim() === "") return false;
6427
+ if (Array.isArray(val) && val.length === 0 && f.type === "array") return false;
6428
+ return true;
6429
+ }).length;
6430
+ return { completed, total };
6431
+ }
5603
6432
  /**
5604
6433
  * Renderiza wizard
5605
6434
  */