hakumi_components 0.1.16.pre → 0.1.17.pre

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.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +169 -23
  3. data/app/assets/javascripts/hakumi_components.js +12 -12
  4. data/app/assets/stylesheets/hakumi_components.css +1 -1
  5. data/app/components/hakumi/alert/component.html.erb +12 -8
  6. data/app/components/hakumi/alert/component.rb +18 -62
  7. data/app/components/hakumi/base_component.rb +13 -0
  8. data/app/components/hakumi/card/component.html.erb +14 -22
  9. data/app/components/hakumi/card/component.rb +38 -31
  10. data/app/components/hakumi/checkbox/component.html.erb +39 -21
  11. data/app/components/hakumi/checkbox/component.rb +12 -2
  12. data/app/components/hakumi/collapse/component.html.erb +2 -2
  13. data/app/components/hakumi/collapse/component.rb +1 -1
  14. data/app/components/hakumi/collapse/panel/component.rb +9 -0
  15. data/app/components/hakumi/color_picker/component.rb +0 -4
  16. data/app/components/hakumi/drawer/component.html.erb +7 -7
  17. data/app/components/hakumi/drawer/component.rb +12 -19
  18. data/app/components/hakumi/input/component.rb +0 -2
  19. data/app/components/hakumi/input/text_area/component.rb +0 -2
  20. data/app/components/hakumi/input_number/component.rb +3 -4
  21. data/app/components/hakumi/mentions/component.rb +0 -1
  22. data/app/components/hakumi/modal/component.html.erb +40 -0
  23. data/app/components/hakumi/modal/component.rb +24 -102
  24. data/app/components/hakumi/modal/confirm/component.html.erb +23 -0
  25. data/app/components/hakumi/modal/confirm/component.rb +23 -41
  26. data/app/components/hakumi/modal/error/component.rb +12 -11
  27. data/app/components/hakumi/modal/info/component.rb +12 -11
  28. data/app/components/hakumi/modal/success/component.rb +12 -11
  29. data/app/components/hakumi/modal/warning/component.rb +15 -10
  30. data/app/components/hakumi/popconfirm/component.html.erb +25 -25
  31. data/app/components/hakumi/popconfirm/component.rb +11 -27
  32. data/app/components/hakumi/rate/component.rb +0 -1
  33. data/app/components/hakumi/segmented/component.rb +0 -4
  34. data/app/components/hakumi/slider/component.rb +2 -6
  35. data/app/components/hakumi/statistic/component.rb +0 -4
  36. data/app/components/hakumi/switch/component.html.erb +4 -0
  37. data/app/components/hakumi/switch/component.rb +1 -2
  38. data/app/components/hakumi/table/component.rb +3 -229
  39. data/app/components/hakumi/table/concerns/columns.rb +1 -1
  40. data/app/components/hakumi/table/concerns/editable.rb +121 -0
  41. data/app/components/hakumi/table/concerns/ellipsis.rb +63 -0
  42. data/app/components/hakumi/table/concerns/fixed_columns.rb +87 -0
  43. data/app/components/hakumi/transfer/component.rb +0 -4
  44. data/app/controllers/{hakumi_components → hakumi}/components_controller.rb +2 -2
  45. data/app/form_builders/hakumi/form_builder.rb +217 -175
  46. data/app/helpers/hakumi/form_helper.rb +39 -0
  47. data/app/javascript/hakumi_components/controllers/base/registry_controller.js +83 -3
  48. data/app/javascript/hakumi_components/controllers/hakumi/affix_controller.js +0 -23
  49. data/app/javascript/hakumi_components/controllers/hakumi/alert_controller.js +2 -1
  50. data/app/javascript/hakumi_components/controllers/hakumi/button_controller.js +0 -7
  51. data/app/javascript/hakumi_components/controllers/hakumi/calendar_controller.js +0 -2
  52. data/app/javascript/hakumi_components/controllers/hakumi/color_picker_controller.js +1 -6
  53. data/app/javascript/hakumi_components/controllers/hakumi/date_picker_controller.js +28 -34
  54. data/app/javascript/hakumi_components/controllers/hakumi/drawer_controller.js +2 -1
  55. data/app/javascript/hakumi_components/controllers/hakumi/form_item_controller.js +9 -63
  56. data/app/javascript/hakumi_components/controllers/hakumi/mentions_controller.js +4 -11
  57. data/app/javascript/hakumi_components/controllers/hakumi/message_controller.js +1 -1
  58. data/app/javascript/hakumi_components/controllers/hakumi/modal_controller.js +4 -20
  59. data/app/javascript/hakumi_components/controllers/hakumi/notification_controller.js +1 -1
  60. data/app/javascript/hakumi_components/controllers/hakumi/popconfirm_controller.js +33 -27
  61. data/app/javascript/hakumi_components/controllers/hakumi/popover_controller.js +2 -23
  62. data/app/javascript/hakumi_components/controllers/hakumi/qr_code_controller.js +0 -20
  63. data/app/javascript/hakumi_components/controllers/hakumi/segmented_controller.js +0 -2
  64. data/app/javascript/hakumi_components/controllers/hakumi/spin_controller.js +1 -19
  65. data/app/javascript/hakumi_components/controllers/hakumi/statistic_controller.js +0 -2
  66. data/app/javascript/hakumi_components/controllers/hakumi/table_controller.js +48 -74
  67. data/app/javascript/hakumi_components/controllers/hakumi/tag_controller.js +15 -14
  68. data/app/javascript/hakumi_components/controllers/hakumi/tag_group_controller.js +14 -13
  69. data/app/javascript/hakumi_components/controllers/hakumi/theme_controller.js +24 -1
  70. data/app/javascript/hakumi_components/controllers/hakumi/time_picker_controller.js +3 -7
  71. data/app/javascript/hakumi_components/controllers/hakumi/timeline_controller.js +0 -16
  72. data/app/javascript/hakumi_components/controllers/hakumi/transfer_controller.js +2 -2
  73. data/app/javascript/hakumi_components/controllers/hakumi/tree_controller.js +0 -2
  74. data/app/javascript/hakumi_components/controllers/hakumi/tree_select_controller.js +3 -3
  75. data/app/javascript/hakumi_components/controllers/hakumi/upload_controller.js +12 -26
  76. data/app/javascript/hakumi_components/core/persistence.js +3 -3
  77. data/app/javascript/hakumi_components/core/render_component.js +3 -1
  78. data/app/javascript/lib/validation_manager.js +101 -0
  79. data/app/javascript/stylesheets/_theme-tokens.scss +2 -1
  80. data/app/javascript/stylesheets/components/_modal.scss +13 -0
  81. data/app/services/{hakumi_components → hakumi}/component_handler.rb +1 -1
  82. data/app/services/hakumi/icon/loader.rb +2 -2
  83. data/app/services/hakumi/illustrations/loader.rb +3 -3
  84. data/app/views/hakumi/_drawer.html.erb +21 -0
  85. data/app/views/hakumi/_modal.html.erb +18 -0
  86. data/lib/hakumi_components/documentation.rb +127 -0
  87. data/lib/hakumi_components/engine.rb +13 -4
  88. data/lib/hakumi_components/rails/attribute_introspection.rb +1 -1
  89. data/lib/hakumi_components/rails/validation_introspection.rb +5 -5
  90. data/lib/hakumi_components/rails/validation_mapper.rb +484 -0
  91. data/lib/hakumi_components/rails.rb +2 -1
  92. data/lib/hakumi_components/version.rb +2 -2
  93. data/lib/hakumi_components.rb +3 -1
  94. data/lib/tasks/coverage.rake +37 -0
  95. data/sig/hakumi/base_component.rbs +5 -0
  96. data/sig/hakumi/checkbox/component.rbs +10 -0
  97. data/sig/hakumi/color_picker/component.rbs +0 -1
  98. data/sig/hakumi/form_builder.rbs +9 -1
  99. data/sig/{hakumi_components → hakumi}/rails/attribute_introspection.rbs +1 -1
  100. data/sig/{hakumi_components → hakumi}/rails/validation_introspection.rbs +1 -1
  101. data/sig/hakumi/rails/validation_mapper.rbs +53 -0
  102. data/sig/{hakumi_components → hakumi}/rails.rbs +1 -1
  103. data/sig/hakumi/segmented/component.rbs +0 -1
  104. data/sig/hakumi/slider/component.rbs +0 -1
  105. data/sig/hakumi/statistic/component.rbs +0 -2
  106. data/sig/hakumi/table/component.rbs +3 -4
  107. data/sig/hakumi/table/concerns/columns.rbs +2 -1
  108. data/sig/hakumi/table/concerns/editable.rbs +40 -0
  109. data/sig/hakumi/table/concerns/ellipsis.rbs +27 -0
  110. data/sig/hakumi/table/concerns/fixed_columns.rbs +33 -0
  111. data/sig/hakumi/transfer/component.rbs +0 -1
  112. data/sig/{hakumi_components.rbs → hakumi.rbs} +20 -3
  113. data/sig/rails/active_model/validations/comparison_validator.rbs +6 -0
  114. metadata +44 -29
  115. data/app/views/hakumi_components/_drawer.html.erb +0 -3
  116. data/app/views/hakumi_components/_modal.html.erb +0 -3
  117. /data/app/views/{hakumi_components → hakumi}/_admin_panel.html.erb +0 -0
  118. /data/app/views/{hakumi_components → hakumi}/_affix.html.erb +0 -0
  119. /data/app/views/{hakumi_components → hakumi}/_alert.html.erb +0 -0
  120. /data/app/views/{hakumi_components → hakumi}/_confirm.html.erb +0 -0
  121. /data/app/views/{hakumi_components → hakumi}/_message.html.erb +0 -0
  122. /data/app/views/{hakumi_components → hakumi}/_notification.html.erb +0 -0
  123. /data/app/views/{hakumi_components → hakumi}/_popconfirm.html.erb +0 -0
  124. /data/app/views/{hakumi_components → hakumi}/_popover.html.erb +0 -0
  125. /data/app/views/{hakumi_components → hakumi}/_qr_code.html.erb +0 -0
  126. /data/app/views/{hakumi_components → hakumi}/_result.html.erb +0 -0
  127. /data/app/views/{hakumi_components → hakumi}/_segmented.html.erb +0 -0
  128. /data/app/views/{hakumi_components → hakumi}/_skeleton.html.erb +0 -0
  129. /data/app/views/{hakumi_components → hakumi}/_spin.html.erb +0 -0
  130. /data/app/views/{hakumi_components → hakumi}/_statistic.html.erb +0 -0
  131. /data/app/views/{hakumi_components → hakumi}/_table.html.erb +0 -0
  132. /data/app/views/{hakumi_components → hakumi}/_tag.html.erb +0 -0
  133. /data/app/views/{hakumi_components → hakumi}/_timeline.html.erb +0 -0
  134. /data/app/views/{hakumi_components → hakumi}/_tree.html.erb +0 -0
@@ -41,13 +41,6 @@ export default class extends RegistryController {
41
41
  setOffsetBottom: (value) => this.setOffsetBottom(value)
42
42
  }
43
43
  }
44
-
45
- this.#exposeApi()
46
- }
47
-
48
- disconnect() {
49
- delete this.element.hakumiAffix
50
- super.disconnect()
51
44
  }
52
45
 
53
46
  updatePosition() {
@@ -187,20 +180,4 @@ export default class extends RegistryController {
187
180
  if (this.hasOffsetBottomValue) return Number(this.offsetBottomValue)
188
181
  return null
189
182
  }
190
-
191
- #exposeApi() {
192
- this.element.hakumiAffix = {
193
- check: () => this.updatePosition(),
194
- update: () => this.updatePosition(),
195
- isAffixed: () => this.affixed,
196
- getState: () => ({
197
- affixed: this.affixed,
198
- offsetTop: this.offsetTop,
199
- offsetBottom: this.offsetBottom,
200
- targetSelector: this.targetSelectorValue || null
201
- }),
202
- setOffsetTop: (value) => this.setOffsetTop(value),
203
- setOffsetBottom: (value) => this.setOffsetBottom(value)
204
- }
205
- }
206
183
  }
@@ -1,6 +1,7 @@
1
1
  import RegistryController from "../base/registry_controller.js"
2
2
 
3
3
  export default class extends RegistryController {
4
+ static declarativeActions = ["close"]
4
5
  static targets = ["message", "description"]
5
6
 
6
7
  registerApi() {
@@ -20,7 +21,7 @@ export default class extends RegistryController {
20
21
 
21
22
  close(event) {
22
23
  if (event) event.preventDefault()
23
- this.element.dispatchEvent(new CustomEvent("hakumi-component:hidden", { bubbles: true }))
24
+ this.dispatch("hidden")
24
25
  this.element.remove()
25
26
  }
26
27
 
@@ -14,13 +14,6 @@ export default class extends RegistryController {
14
14
  this.element.removeEventListener('click', this.boundRipple)
15
15
  }
16
16
 
17
- handleClick(event) {
18
- if (this.loadingValue || this.element.disabled) {
19
- event.preventDefault()
20
- event.stopImmediatePropagation()
21
- }
22
- }
23
-
24
17
  createRipple(event) {
25
18
  const button = event.currentTarget
26
19
  const circle = document.createElement("span")
@@ -41,7 +41,6 @@ export default class extends RegistryController {
41
41
  }
42
42
 
43
43
  teardown() {
44
- delete this.element.hakumiCalendar
45
44
  }
46
45
 
47
46
  #bindNestedEvents() {
@@ -1096,6 +1095,5 @@ export default class extends RegistryController {
1096
1095
  }
1097
1096
 
1098
1097
 
1099
- this.element.hakumiCalendar = api
1100
1098
  }
1101
1099
  }
@@ -725,11 +725,6 @@ export default class extends RegistryController {
725
725
  }
726
726
 
727
727
  dispatchEvent(name, detail) {
728
- this.element.dispatchEvent(
729
- new CustomEvent(`hakumi:color-picker:${name}`, {
730
- detail,
731
- bubbles: true
732
- })
733
- )
728
+ this.dispatch(name, { detail })
734
729
  }
735
730
  }
@@ -28,7 +28,7 @@ export default class extends RegistryController {
28
28
  teardown() {
29
29
  document.removeEventListener("click", this.boundClickOutside)
30
30
 
31
- // Limpiar listeners de calendarios si existen
31
+ // Clean up calendar listeners if they exist
32
32
  if (this.rangeValue && this.hasPanelTarget) {
33
33
  const calendars = this.panelTarget.querySelectorAll(".hakumi-calendar")
34
34
  const [startCalendar, endCalendar] = calendars
@@ -355,13 +355,13 @@ export default class extends RegistryController {
355
355
  if (!api) return
356
356
 
357
357
  if (this.tempRangeStart && this.tempRangeEnd) {
358
- // Rango completo
358
+ // Complete range
359
359
  api.setRange(this.tempRangeStart, this.tempRangeEnd, null)
360
360
  } else if (this.tempRangeStart) {
361
- // Solo inicio, sin fin (mostrar solo el inicio)
361
+ // Start only, no end (show only start)
362
362
  api.setRange(this.tempRangeStart, null, null)
363
363
  } else {
364
- // Sin rango
364
+ // No range
365
365
  api.clearRange()
366
366
  }
367
367
  })
@@ -391,12 +391,12 @@ export default class extends RegistryController {
391
391
 
392
392
  if (!startApi || !endApi) return
393
393
 
394
- // Obtener el mes/año actual del primer calendario
394
+ // Get current month/year from first calendar
395
395
  const startDate = startApi.getValue()
396
396
  const startMonth = startDate.getMonth()
397
397
  const startYear = startDate.getFullYear()
398
398
 
399
- // Calcular el mes siguiente para el segundo calendario
399
+ // Calculate next month for second calendar
400
400
  let endMonth = startMonth + 1
401
401
  let endYear = startYear
402
402
 
@@ -405,7 +405,7 @@ export default class extends RegistryController {
405
405
  endYear += 1
406
406
  }
407
407
 
408
- // Establecer el segundo calendario al mes siguiente
408
+ // Set second calendar to next month
409
409
  const endDate = new Date(endYear, endMonth, 1)
410
410
  endApi.setValue(endDate, { navigate: true })
411
411
  }
@@ -419,7 +419,7 @@ export default class extends RegistryController {
419
419
  const startCalendar = calendars[0]
420
420
  const endCalendar = calendars[1]
421
421
 
422
- // Remover listeners previos si existen
422
+ // Remove previous listeners if they exist
423
423
  if (this.boundSyncFromStart) {
424
424
  startCalendar.removeEventListener("hakumi--calendar:change", this.boundSyncFromStart)
425
425
  }
@@ -427,7 +427,7 @@ export default class extends RegistryController {
427
427
  endCalendar.removeEventListener("hakumi--calendar:change", this.boundSyncFromEnd)
428
428
  }
429
429
 
430
- // Crear los listeners
430
+ // Create listeners
431
431
  this.boundSyncFromStart = (event) => {
432
432
  const startApi = startCalendar.hakumiComponent?.api
433
433
  const endApi = endCalendar.hakumiComponent?.api
@@ -437,7 +437,7 @@ export default class extends RegistryController {
437
437
  const startMonth = startDate.getMonth()
438
438
  const startYear = startDate.getFullYear()
439
439
 
440
- // Calcular el mes siguiente
440
+ // Calculate next month
441
441
  let endMonth = startMonth + 1
442
442
  let endYear = startYear
443
443
 
@@ -450,7 +450,7 @@ export default class extends RegistryController {
450
450
  const currentEndMonth = endDate.getMonth()
451
451
  const currentEndYear = endDate.getFullYear()
452
452
 
453
- // Solo sincronizar si el segundo calendario no está ya en el mes correcto
453
+ // Only sync if second calendar is not already on the correct month
454
454
  if (currentEndMonth !== endMonth || currentEndYear !== endYear) {
455
455
  endApi.setValue(new Date(endYear, endMonth, 1), { navigate: true })
456
456
  }
@@ -465,7 +465,7 @@ export default class extends RegistryController {
465
465
  const endMonth = endDate.getMonth()
466
466
  const endYear = endDate.getFullYear()
467
467
 
468
- // Calcular el mes anterior
468
+ // Calculate previous month
469
469
  let startMonth = endMonth - 1
470
470
  let startYear = endYear
471
471
 
@@ -478,13 +478,13 @@ export default class extends RegistryController {
478
478
  const currentStartMonth = startDate.getMonth()
479
479
  const currentStartYear = startDate.getFullYear()
480
480
 
481
- // Solo sincronizar si el primer calendario no está ya en el mes correcto
481
+ // Only sync if first calendar is not already on the correct month
482
482
  if (currentStartMonth !== startMonth || currentStartYear !== startYear) {
483
483
  startApi.setValue(new Date(startYear, startMonth, 1), { navigate: true })
484
484
  }
485
485
  }
486
486
 
487
- // Listener para hover
487
+ // Hover listener
488
488
  this.boundHandleHover = (event) => {
489
489
  const hoveredDate = event.detail?.date
490
490
  if (!hoveredDate) return
@@ -602,23 +602,23 @@ export default class extends RegistryController {
602
602
  const formatted = this.formatDate(date)
603
603
 
604
604
  if (this.rangeValue) {
605
- // Nueva lógica de selección de rango visual
605
+ // New visual range selection logic
606
606
  if (!this.tempRangeStart) {
607
- // Primera selección: establecer inicio del rango
607
+ // First selection: set range start
608
608
  this.tempRangeStart = date
609
609
  this.tempRangeEnd = null
610
610
 
611
- // Actualizar input de inicio
611
+ // Update start input
612
612
  if (this.hasStartInputTarget) {
613
613
  this.startInputTarget.value = formatted
614
614
  }
615
615
 
616
- // Limpiar input de fin
616
+ // Clear end input
617
617
  if (this.hasEndInputTarget) {
618
618
  this.endInputTarget.value = ""
619
619
  }
620
620
 
621
- // Actualizar visual en ambos calendarios
621
+ // Update visual in both calendars
622
622
  this.updateRangeVisuals()
623
623
 
624
624
  this.dispatchChangeEvent({
@@ -626,10 +626,10 @@ export default class extends RegistryController {
626
626
  endValue: ""
627
627
  })
628
628
  } else if (!this.tempRangeEnd) {
629
- // Segunda selección: establecer fin del rango
629
+ // Second selection: set range end
630
630
  const clickedDate = date
631
631
 
632
- // Si la fecha clickeada es anterior al inicio, reiniciar selección
632
+ // If clicked date is before start, restart selection
633
633
  if (clickedDate < this.tempRangeStart) {
634
634
  this.tempRangeStart = clickedDate
635
635
  this.tempRangeEnd = null
@@ -648,7 +648,7 @@ export default class extends RegistryController {
648
648
  endValue: ""
649
649
  })
650
650
  } else {
651
- // Completar el rango
651
+ // Complete the range
652
652
  this.tempRangeEnd = clickedDate
653
653
 
654
654
  if (this.hasEndInputTarget) {
@@ -664,11 +664,11 @@ export default class extends RegistryController {
664
664
 
665
665
  this.updateClearVisibility()
666
666
 
667
- // Cerrar el picker después de completar la selección
667
+ // Close picker after completing selection
668
668
  this.close()
669
669
  }
670
670
  } else {
671
- // Ya hay un rango completo, reiniciar con nueva selección
671
+ // Already have a complete range, restart with new selection
672
672
  this.tempRangeStart = date
673
673
  this.tempRangeEnd = null
674
674
 
@@ -688,10 +688,10 @@ export default class extends RegistryController {
688
688
  }
689
689
 
690
690
  this.updateClearVisibility()
691
- return // Salir temprano para rangos
691
+ return // Early return for ranges
692
692
  }
693
693
 
694
- // Código para pickers no-rango
694
+ // Code for non-range pickers
695
695
  // Use setValue to update both hidden and display inputs
696
696
  this.setValue(formatted)
697
697
  this.syncCalendarWithInput()
@@ -781,17 +781,11 @@ export default class extends RegistryController {
781
781
  }
782
782
 
783
783
  dispatchChangeEvent(detail) {
784
- this.element.dispatchEvent(new CustomEvent("hakumi:date-picker:change", {
785
- bubbles: true,
786
- detail
787
- }))
784
+ this.dispatch("change", { detail })
788
785
  }
789
786
 
790
787
  dispatchOpenChange(open) {
791
- this.element.dispatchEvent(new CustomEvent("hakumi:date-picker:open-change", {
792
- bubbles: true,
793
- detail: { open }
794
- }))
788
+ this.dispatch("openChange", { detail: { open } })
795
789
  }
796
790
 
797
791
  // DateTime methods (for show_time: true)
@@ -2,6 +2,7 @@ import RegistryController from "../base/registry_controller.js"
2
2
  import { ensureOverlayContainer } from "../../core/overlay_container.js"
3
3
 
4
4
  export default class extends RegistryController {
5
+ static declarativeActions = ["close"]
5
6
  static targets = ["mask", "wrapper", "body"]
6
7
  static values = {
7
8
  open: Boolean,
@@ -103,7 +104,7 @@ export default class extends RegistryController {
103
104
  document.body.style.overflow = ""
104
105
  document.body.style.userSelect = ""
105
106
  document.body.style.webkitUserSelect = ""
106
- this.element.dispatchEvent(new CustomEvent("hakumi-component:hidden", { bubbles: true }))
107
+ this.dispatch("hidden")
107
108
  }
108
109
  this.closeTimeout = null
109
110
  }, 300)
@@ -1,8 +1,6 @@
1
1
  import RegistryController from "../base/registry_controller.js"
2
2
  import { validationManager } from "../../../lib/validation_manager.js"
3
3
 
4
-
5
-
6
4
  export default class extends RegistryController {
7
5
  static values = {
8
6
  dependencies: Array,
@@ -11,14 +9,7 @@ export default class extends RegistryController {
11
9
  }
12
10
 
13
11
  setup() {
14
- console.log('[FormItemController] Setup', {
15
- fieldName: this.fieldNameValue,
16
- dependencies: this.dependenciesValue,
17
- rules: this.rulesValue
18
- })
19
-
20
12
  this.hasInteracted = false
21
-
22
13
  this.setupListeners()
23
14
  this.setupFormSubmitValidation()
24
15
  }
@@ -61,10 +52,8 @@ export default class extends RegistryController {
61
52
  }
62
53
 
63
54
  handleFormSubmit(event) {
64
-
65
55
  this.hasInteracted = true
66
56
 
67
-
68
57
  const currentField = this.element.querySelector('input, textarea')
69
58
  if (!currentField) return
70
59
 
@@ -77,7 +66,6 @@ export default class extends RegistryController {
77
66
  if (!result.valid) {
78
67
  event.preventDefault()
79
68
  this.showError(result.errors[0])
80
- console.log('[FormItemController] Form submission prevented due to validation error')
81
69
  }
82
70
  }
83
71
  }
@@ -85,29 +73,32 @@ export default class extends RegistryController {
85
73
  setupListeners() {
86
74
  this.boundValidate = this.validate.bind(this)
87
75
 
88
-
89
76
  if (this.hasRulesValue && this.rulesValue.length > 0) {
90
77
  const currentField = this.element.querySelector('input, textarea')
91
78
  if (currentField) {
92
79
  currentField.addEventListener('input', this.boundValidate)
93
80
  currentField.addEventListener('blur', this.boundValidate)
94
- console.log('[FormItemController] Attached validation listeners to current field')
95
81
  }
96
82
  }
97
83
 
98
-
99
84
  let dependencies = []
100
85
 
101
-
102
86
  if (this.hasDependenciesValue) {
103
87
  dependencies.push(...this.dependenciesValue)
104
88
  }
105
89
 
106
-
107
90
  if (this.hasRulesValue) {
108
91
  this.rulesValue.forEach(rule => {
109
92
  if (rule.match) {
110
- dependencies.push(rule.match)
93
+ if (rule.match.field) {
94
+ dependencies.push(rule.match.field)
95
+ } else if (typeof rule.match === 'string') {
96
+ dependencies.push(rule.match)
97
+ }
98
+ }
99
+
100
+ if (rule.comparison && rule.comparison.field) {
101
+ dependencies.push(rule.comparison.field)
111
102
  }
112
103
 
113
104
  if (rule.dependent && rule.dependent.field) {
@@ -116,36 +107,26 @@ export default class extends RegistryController {
116
107
  })
117
108
  }
118
109
 
119
-
120
110
  dependencies = [...new Set(dependencies)]
121
111
 
122
112
  if (dependencies.length === 0) {
123
113
  return
124
114
  }
125
115
 
126
- console.log('[FormItemController] Setting up dependency listeners for:', dependencies)
127
-
128
116
  this.dependencyFields = []
129
117
 
130
-
131
118
  const currentField = this.element.querySelector('input, textarea')
132
119
  if (currentField) {
133
120
  currentField.addEventListener('input', this.boundValidate)
134
121
  currentField.addEventListener('change', this.boundValidate)
135
- console.log('[FormItemController] Attached listeners to current field')
136
122
  }
137
123
 
138
-
139
124
  dependencies.forEach(depFieldName => {
140
125
  const depField = this.findField(depFieldName)
141
- console.log('[FormItemController] Looking for field:', depFieldName, 'Found:', depField)
142
126
  if (depField) {
143
127
  this.dependencyFields.push(depField)
144
128
  depField.addEventListener('input', this.boundValidate)
145
129
  depField.addEventListener('change', this.boundValidate)
146
- console.log('[FormItemController] Attached listeners to:', depFieldName)
147
- } else {
148
- console.warn('[FormItemController] Could not find dependency field:', depFieldName)
149
130
  }
150
131
  })
151
132
  }
@@ -153,7 +134,6 @@ export default class extends RegistryController {
153
134
  removeListeners() {
154
135
  if (!this.boundValidate) return
155
136
 
156
-
157
137
  const currentField = this.element.querySelector('input, textarea')
158
138
  if (currentField) {
159
139
  currentField.removeEventListener('input', this.boundValidate)
@@ -161,7 +141,6 @@ export default class extends RegistryController {
161
141
  currentField.removeEventListener('change', this.boundValidate)
162
142
  }
163
143
 
164
-
165
144
  if (!this.dependencyFields) return
166
145
 
167
146
  this.dependencyFields.forEach(field => {
@@ -171,62 +150,44 @@ export default class extends RegistryController {
171
150
  }
172
151
 
173
152
  findField(fieldName) {
174
-
175
153
  const form = this.element.closest('form')
176
154
  if (!form) return null
177
155
 
178
-
179
156
  let field = form.querySelector(`[name="${fieldName}"]`)
180
157
  if (field) return field
181
158
 
182
-
183
159
  field = form.querySelector(`#${fieldName}`)
184
160
  if (field) return field
185
161
 
186
-
187
162
  field = form.querySelector(`[name*="[${fieldName}]"]`)
188
163
  if (field) return field
189
164
 
190
-
191
165
  field = form.querySelector(`[name$="${fieldName}"]`)
192
166
  return field
193
167
  }
194
168
 
195
169
  validate() {
196
- console.log('[FormItemController] validate() called, hasInteracted:', this.hasInteracted)
197
-
198
-
199
170
  if (!this.hasInteracted) {
200
171
  this.hasInteracted = true
201
172
  }
202
173
 
203
174
  const currentField = this.element.querySelector('input, textarea')
204
175
  if (!currentField) {
205
- console.warn('[FormItemController] No input/textarea found in element')
206
176
  return
207
177
  }
208
- console.log('[FormItemController] Current field:', currentField)
209
178
 
210
179
  const value = currentField.value
211
180
 
212
-
213
181
  if (this.hasRulesValue && this.rulesValue.length > 0) {
214
- console.log('[FormItemController] Running rule-based validation')
215
182
  this.validateWithRules(value)
216
183
  }
217
184
  }
218
185
 
219
186
  validateWithRules(value) {
220
-
221
187
  const formData = this.collectFormData()
222
-
223
-
224
188
  const result = validationManager.validate(value, this.rulesValue, formData)
225
189
 
226
- console.log('[FormItemController] Validation result:', result)
227
-
228
190
  if (!result.valid) {
229
-
230
191
  this.showError(result.errors[0])
231
192
  } else {
232
193
  this.clearError()
@@ -242,7 +203,6 @@ export default class extends RegistryController {
242
203
 
243
204
  inputs.forEach(input => {
244
205
  if (input.name) {
245
-
246
206
  const match = input.name.match(/\[([^\]]+)\]$/)
247
207
  const fieldName = match ? match[1] : input.name
248
208
  formData[fieldName] = input.value
@@ -253,10 +213,8 @@ export default class extends RegistryController {
253
213
  }
254
214
 
255
215
  showError(message) {
256
-
257
216
  this.element.classList.add('hakumi-form-item-has-error')
258
217
 
259
-
260
218
  let errorContainer = this.element.querySelector('.hakumi-form-item-explain')
261
219
  if (!errorContainer) {
262
220
  const controlDiv = this.element.querySelector('.hakumi-form-item-control')
@@ -279,26 +237,21 @@ export default class extends RegistryController {
279
237
  errorElement.textContent = message
280
238
  }
281
239
 
282
-
283
240
  const input = this.element.querySelector('input, textarea')
284
241
  if (input) {
285
242
  input.setAttribute('aria-invalid', 'true')
286
243
  }
287
244
 
288
-
289
245
  this.updateSubmitButton(true)
290
246
  }
291
247
 
292
248
  clearError() {
293
-
294
249
  this.element.classList.remove('hakumi-form-item-has-error')
295
250
 
296
-
297
251
  const errorContainer = this.element.querySelector('.hakumi-form-item-explain')
298
252
  if (errorContainer) {
299
253
  const errorElement = errorContainer.querySelector('.hakumi-form-item-explain-error')
300
254
  if (errorElement) {
301
-
302
255
  const caption = this.element.querySelector('.hakumi-form-item-extra')
303
256
  if (caption) {
304
257
  errorContainer.innerHTML = ''
@@ -309,13 +262,11 @@ export default class extends RegistryController {
309
262
  }
310
263
  }
311
264
 
312
-
313
265
  const input = this.element.querySelector('input, textarea')
314
266
  if (input) {
315
267
  input.removeAttribute('aria-invalid')
316
268
  }
317
269
 
318
-
319
270
  this.updateSubmitButton(false)
320
271
  }
321
272
 
@@ -328,15 +279,10 @@ export default class extends RegistryController {
328
279
 
329
280
  if (hasError) {
330
281
  submitButton.setAttribute('disabled', 'disabled')
331
- console.log('[FormItemController] Submit button disabled')
332
282
  } else {
333
-
334
283
  const otherErrors = form.querySelectorAll('.hakumi-form-item-has-error')
335
284
  if (otherErrors.length === 0) {
336
285
  submitButton.removeAttribute('disabled')
337
- console.log('[FormItemController] Submit button enabled')
338
- } else {
339
- console.log('[FormItemController] Submit button still disabled (other errors present)')
340
286
  }
341
287
  }
342
288
  }
@@ -288,27 +288,20 @@ export default class extends RegistryController {
288
288
  }
289
289
 
290
290
  #dispatchChange() {
291
- this.element.dispatchEvent(new CustomEvent("hakumi:mentions:change", {
292
- bubbles: true,
291
+ this.dispatch("change", {
293
292
  detail: {
294
293
  value: this.getValue(),
295
294
  mentions: this.getMentions()
296
295
  }
297
- }))
296
+ })
298
297
  }
299
298
 
300
299
  #dispatchSelect(detail) {
301
- this.element.dispatchEvent(new CustomEvent("hakumi:mentions:select", {
302
- bubbles: true,
303
- detail
304
- }))
300
+ this.dispatch("select", { detail })
305
301
  }
306
302
 
307
303
  #dispatchSearch(detail) {
308
- this.element.dispatchEvent(new CustomEvent("hakumi:mentions:search", {
309
- bubbles: true,
310
- detail
311
- }))
304
+ this.dispatch("search", { detail })
312
305
  }
313
306
 
314
307
  #positionDropdown() {
@@ -270,7 +270,7 @@ export default class extends RegistryController {
270
270
  }
271
271
 
272
272
  #dispatchHidden() {
273
- this.element.dispatchEvent(new CustomEvent("hakumi-component:hidden", { bubbles: true }))
273
+ this.dispatch("hidden")
274
274
  }
275
275
 
276
276
  #enforceMaxCount() {