@hakumi-dev/hakumi-components 0.1.16-pre → 0.1.18-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 (36) hide show
  1. package/README.md +196 -28
  2. package/app/javascript/hakumi_components/controllers/base/registry_controller.js +83 -3
  3. package/app/javascript/hakumi_components/controllers/hakumi/affix_controller.js +0 -23
  4. package/app/javascript/hakumi_components/controllers/hakumi/alert_controller.js +2 -1
  5. package/app/javascript/hakumi_components/controllers/hakumi/button_controller.js +0 -7
  6. package/app/javascript/hakumi_components/controllers/hakumi/calendar_controller.js +0 -2
  7. package/app/javascript/hakumi_components/controllers/hakumi/color_picker_controller.js +1 -6
  8. package/app/javascript/hakumi_components/controllers/hakumi/date_picker_controller.js +28 -34
  9. package/app/javascript/hakumi_components/controllers/hakumi/drawer_controller.js +2 -1
  10. package/app/javascript/hakumi_components/controllers/hakumi/form_item_controller.js +9 -63
  11. package/app/javascript/hakumi_components/controllers/hakumi/mentions_controller.js +4 -11
  12. package/app/javascript/hakumi_components/controllers/hakumi/message_controller.js +1 -1
  13. package/app/javascript/hakumi_components/controllers/hakumi/modal_controller.js +4 -20
  14. package/app/javascript/hakumi_components/controllers/hakumi/notification_controller.js +1 -1
  15. package/app/javascript/hakumi_components/controllers/hakumi/popconfirm_controller.js +33 -27
  16. package/app/javascript/hakumi_components/controllers/hakumi/popover_controller.js +2 -23
  17. package/app/javascript/hakumi_components/controllers/hakumi/qr_code_controller.js +0 -20
  18. package/app/javascript/hakumi_components/controllers/hakumi/segmented_controller.js +0 -2
  19. package/app/javascript/hakumi_components/controllers/hakumi/spin_controller.js +1 -19
  20. package/app/javascript/hakumi_components/controllers/hakumi/statistic_controller.js +0 -2
  21. package/app/javascript/hakumi_components/controllers/hakumi/table_controller.js +48 -74
  22. package/app/javascript/hakumi_components/controllers/hakumi/tag_controller.js +15 -14
  23. package/app/javascript/hakumi_components/controllers/hakumi/tag_group_controller.js +14 -13
  24. package/app/javascript/hakumi_components/controllers/hakumi/theme_controller.js +24 -1
  25. package/app/javascript/hakumi_components/controllers/hakumi/time_picker_controller.js +3 -7
  26. package/app/javascript/hakumi_components/controllers/hakumi/timeline_controller.js +0 -16
  27. package/app/javascript/hakumi_components/controllers/hakumi/transfer_controller.js +2 -2
  28. package/app/javascript/hakumi_components/controllers/hakumi/tree_controller.js +0 -2
  29. package/app/javascript/hakumi_components/controllers/hakumi/tree_select_controller.js +3 -3
  30. package/app/javascript/hakumi_components/controllers/hakumi/upload_controller.js +12 -26
  31. package/app/javascript/hakumi_components/core/persistence.js +3 -3
  32. package/app/javascript/hakumi_components/core/render_component.js +3 -1
  33. package/app/javascript/lib/validation_manager.js +101 -0
  34. package/app/javascript/stylesheets/_theme-tokens.scss +2 -1
  35. package/app/javascript/stylesheets/components/_modal.scss +13 -0
  36. package/package.json +1 -1
@@ -301,7 +301,7 @@ export default class extends RegistryController {
301
301
  direction
302
302
  }
303
303
 
304
- this.element.dispatchEvent(new CustomEvent("hakumi:transfer:change", { detail, bubbles: true }))
304
+ this.dispatch("change", { detail })
305
305
  }
306
306
 
307
307
  #dispatchSelectChange() {
@@ -310,6 +310,6 @@ export default class extends RegistryController {
310
310
  targetSelectedKeys: this.#selectedKeys("target")
311
311
  }
312
312
 
313
- this.element.dispatchEvent(new CustomEvent("hakumi:transfer:select-change", { detail, bubbles: true }))
313
+ this.dispatch("selectChange", { detail })
314
314
  }
315
315
  }
@@ -51,7 +51,6 @@ export default class extends RegistryController {
51
51
  }
52
52
 
53
53
  disconnect() {
54
- delete this.element.hakumiTree
55
54
  super.disconnect()
56
55
  }
57
56
 
@@ -81,7 +80,6 @@ export default class extends RegistryController {
81
80
  api
82
81
  }
83
82
 
84
- this.element.hakumiTree = api
85
83
  }
86
84
 
87
85
  buildNodeMap() {
@@ -268,14 +268,14 @@ export default class extends RegistryController {
268
268
  }
269
269
 
270
270
  treeApi() {
271
- return this.treeTarget?.hakumiTree || this.treeTarget?.hakumiComponent?.api
271
+ return this.treeTarget?.hakumiComponent?.api
272
272
  }
273
273
 
274
274
  dispatchSelect(detail) {
275
- this.element.dispatchEvent(new CustomEvent("hakumi:tree-select:select", { detail, bubbles: true }))
275
+ this.dispatch("select", { detail })
276
276
  }
277
277
 
278
278
  dispatchChange(detail) {
279
- this.element.dispatchEvent(new CustomEvent("hakumi:tree-select:change", { detail, bubbles: true }))
279
+ this.dispatch("change", { detail })
280
280
  }
281
281
  }
@@ -742,31 +742,21 @@ export default class extends RegistryController {
742
742
  }
743
743
 
744
744
  dispatchChange(file) {
745
- this.element.dispatchEvent(
746
- new CustomEvent("hakumi:upload:change", {
747
- bubbles: true,
748
- detail: { file, fileList: this.getFileList() }
749
- })
750
- )
745
+ this.dispatch("change", {
746
+ detail: { file, fileList: this.getFileList() }
747
+ })
751
748
  }
752
749
 
753
750
  dispatchProgress(file, event) {
754
- this.element.dispatchEvent(
755
- new CustomEvent("hakumi:upload:progress", {
756
- bubbles: true,
757
- detail: { file: file ? { ...file } : null, fileList: this.getFileList(), event }
758
- })
759
- )
751
+ this.dispatch("progress", {
752
+ detail: { file: file ? { ...file } : null, fileList: this.getFileList(), event }
753
+ })
760
754
  }
761
755
 
762
756
  dispatchSuccess(file) {
763
- this.element.dispatchEvent(
764
- new CustomEvent("hakumi:upload:success", {
765
- bubbles: true,
766
- detail: { file: file ? { ...file } : null, fileList: this.getFileList() }
767
- })
768
- )
769
-
757
+ this.dispatch("success", {
758
+ detail: { file: file ? { ...file } : null, fileList: this.getFileList() }
759
+ })
770
760
 
771
761
  if (window.HakumiComponents?.renderComponent && file?.name) {
772
762
  window.HakumiComponents.renderComponent("message", {
@@ -781,13 +771,9 @@ export default class extends RegistryController {
781
771
  }
782
772
 
783
773
  dispatchError(file, error) {
784
- this.element.dispatchEvent(
785
- new CustomEvent("hakumi:upload:error", {
786
- bubbles: true,
787
- detail: { file: file ? { ...file } : null, fileList: this.getFileList(), error }
788
- })
789
- )
790
-
774
+ this.dispatch("error", {
775
+ detail: { file: file ? { ...file } : null, fileList: this.getFileList(), error }
776
+ })
791
777
 
792
778
  if (window.HakumiComponents?.renderComponent) {
793
779
  const errorMsg = error || file?.error || "Upload failed"
@@ -21,7 +21,7 @@ const json = {
21
21
  },
22
22
  }
23
23
 
24
- // --- navegación (Turbo + fallbacks), bind único global al módulo ---
24
+ // Navigation events (Turbo + fallbacks), single global binding per module
25
25
  let navBound = false
26
26
  const navCallbacks = new Set()
27
27
 
@@ -51,7 +51,7 @@ const bindNavigationOnce = () => {
51
51
  window.addEventListener("hashchange", scheduleNavCallbacks)
52
52
  }
53
53
 
54
- // Dedupe por key: evita múltiples restores activos si mount() corre más de una vez
54
+ // Dedupe by key: prevents multiple active restores if mount() runs more than once
55
55
  const bootstrapsByKey = new Map() // key -> unsubscribe
56
56
 
57
57
  export const Persistence = {
@@ -80,7 +80,7 @@ export const Persistence = {
80
80
  throw new Error("[HakumiComponents] Persistence.bootstrap(key, fn) expects a function")
81
81
  }
82
82
 
83
- // dedupe
83
+ // Dedupe: return existing unsubscribe if already bootstrapped
84
84
  const existing = bootstrapsByKey.get(key)
85
85
  if (existing) return existing
86
86
 
@@ -149,7 +149,9 @@ export const renderComponent = async (
149
149
  element.remove()
150
150
  unregister(id)
151
151
  }
152
- element.addEventListener("hakumi-component:hidden", handleHidden, { once: true })
152
+ // Listen for component-specific hidden event (e.g., hakumi--modal:hidden)
153
+ const controllerName = name.replace(/_/g, "-")
154
+ element.addEventListener(`hakumi--${controllerName}:hidden`, handleHidden, { once: true })
153
155
  }
154
156
 
155
157
  await nextFrame()
@@ -106,6 +106,107 @@ export default class ValidationManager {
106
106
  }
107
107
  },
108
108
 
109
+ comparison: (value, rule, formData = {}) => {
110
+ if (value === null || value === undefined || value === '') return { valid: true }
111
+
112
+ const config = rule.value || rule.comparison || {}
113
+ const operator = config.operator
114
+ if (!operator) return { valid: true }
115
+
116
+ let targetValue
117
+ if (config.field) {
118
+ targetValue = formData[config.field]
119
+ } else if (config.value !== undefined) {
120
+ targetValue = config.value
121
+ } else {
122
+ return { valid: true }
123
+ }
124
+
125
+ if (targetValue === null || targetValue === undefined || targetValue === '') {
126
+ return { valid: true }
127
+ }
128
+
129
+ const normalize = (raw) => {
130
+ if (raw instanceof Date) {
131
+ return { type: 'date', value: raw.getTime() }
132
+ }
133
+
134
+ if (typeof raw === 'number') {
135
+ return { type: 'number', value: raw }
136
+ }
137
+
138
+ const text = String(raw).trim()
139
+ if (text === '') return { type: 'empty', value: text }
140
+
141
+ const numeric = Number(text)
142
+ if (!Number.isNaN(numeric) && /^-?\d+(\.\d+)?$/.test(text)) {
143
+ return { type: 'number', value: numeric }
144
+ }
145
+
146
+ const parsedDate = Date.parse(text)
147
+ if (!Number.isNaN(parsedDate) && /[-/T:]/.test(text)) {
148
+ return { type: 'date', value: parsedDate }
149
+ }
150
+
151
+ return { type: 'string', value: text }
152
+ }
153
+
154
+ const left = normalize(value)
155
+ const right = normalize(targetValue)
156
+
157
+ if (left.type === 'empty' || right.type === 'empty') return { valid: true }
158
+
159
+ const compareValues = (a, b) => {
160
+ if (a.type === 'number' && b.type === 'number') {
161
+ return a.value === b.value ? 0 : (a.value > b.value ? 1 : -1)
162
+ }
163
+
164
+ if (a.type === 'date' && b.type === 'date') {
165
+ return a.value === b.value ? 0 : (a.value > b.value ? 1 : -1)
166
+ }
167
+
168
+ if (a.value === b.value) return 0
169
+ return a.value > b.value ? 1 : -1
170
+ }
171
+
172
+ const result = compareValues(left, right)
173
+ let valid
174
+
175
+ switch (operator) {
176
+ case 'greater_than':
177
+ valid = result > 0
178
+ break
179
+ case 'greater_than_or_equal_to':
180
+ valid = result >= 0
181
+ break
182
+ case 'less_than':
183
+ valid = result < 0
184
+ break
185
+ case 'less_than_or_equal_to':
186
+ valid = result <= 0
187
+ break
188
+ case 'equal_to':
189
+ valid = result === 0
190
+ break
191
+ case 'other_than':
192
+ valid = result !== 0
193
+ break
194
+ default:
195
+ valid = true
196
+ break
197
+ }
198
+
199
+ const label = config.label || config.field || config.value
200
+ const fallbackMessage = label
201
+ ? `Must be ${operator.replace(/_/g, ' ')} ${label}`
202
+ : 'Comparison failed'
203
+
204
+ return {
205
+ valid,
206
+ message: rule.message || fallbackMessage
207
+ }
208
+ },
209
+
109
210
  custom: (value, rule) => {
110
211
  if (typeof rule.validator !== 'function') {
111
212
  console.error('Custom validator must be a function')
@@ -252,8 +252,9 @@
252
252
 
253
253
 
254
254
 
255
+ // Auto-detect theme from OS preference (opt-in with data-theme="auto")
255
256
  @media (prefers-color-scheme: dark) {
256
- :root:not([data-theme]) {
257
+ :root[data-theme="auto"] {
257
258
 
258
259
  --color-primary: #6366f1;
259
260
  --color-primary-hover: #818cf8;
@@ -207,4 +207,17 @@
207
207
  transform: scale(0.2);
208
208
  transition: opacity 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
209
209
  transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
210
+ }
211
+
212
+ // Modal Confirm variant
213
+ .hakumi-modal-confirm-body-wrapper {
214
+ display: flex;
215
+ align-items: flex-start;
216
+ gap: 12px;
217
+ }
218
+
219
+ .hakumi-modal-confirm-content {
220
+ p {
221
+ margin: 0;
222
+ }
210
223
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hakumi-dev/hakumi-components",
3
- "version": "0.1.16-pre",
3
+ "version": "0.1.18-pre",
4
4
  "type": "module",
5
5
  "description": "Hakumi Components for Rails and modern JavaScript frameworks",
6
6
  "keywords": [