@hakumi-dev/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 (36) hide show
  1. package/README.md +169 -23
  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
@@ -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 = ["confirm", "cancel"]
4
5
  static targets = ["trigger", "popover", "okButton", "cancelButton"]
5
6
  static values = {
6
7
  placement: { type: String, default: "top" },
@@ -29,9 +30,12 @@ export default class extends RegistryController {
29
30
  this.isOpen = this.openValue
30
31
  this.isLoading = false
31
32
  this.setupTriggerEvents()
32
- this.setupButtonEvents()
33
33
  this._popover = this.popoverTarget
34
34
 
35
+ // Listen for declarative actions on popover (may be moved to document.body)
36
+ this.boundPopoverAction = this.handleDeclarativeAction.bind(this)
37
+ this._popover.addEventListener("click", this.boundPopoverAction)
38
+
35
39
  if (this.isOpen) {
36
40
  this.show()
37
41
  }
@@ -39,13 +43,37 @@ export default class extends RegistryController {
39
43
 
40
44
  teardown() {
41
45
  this.removeTriggerEvents()
42
- this.removeButtonEvents()
46
+
47
+ if (this._popover) {
48
+ this._popover.removeEventListener("click", this.boundPopoverAction)
49
+ }
43
50
 
44
51
  if (this._popover && this._popover.parentElement === document.body) {
45
52
  this.element.appendChild(this._popover)
46
53
  }
47
54
  }
48
55
 
56
+ // Override to also check popover that may be moved to document.body
57
+ handleDeclarativeAction(event) {
58
+ const actionElement = event.target.closest("[data-hakumi-action]")
59
+ if (!actionElement) return
60
+
61
+ // Check if action element is inside this component OR inside our popover
62
+ const isInComponent = this.element.contains(actionElement)
63
+ const isInPopover = this._popover && this._popover.contains(actionElement)
64
+
65
+ if (!isInComponent && !isInPopover) return
66
+
67
+ const action = actionElement.dataset.hakumiAction
68
+ const actionMethod = this.getActionMethod(action)
69
+
70
+ if (typeof actionMethod === "function") {
71
+ event.preventDefault()
72
+ event.stopPropagation()
73
+ actionMethod.call(this, event, actionElement)
74
+ }
75
+ }
76
+
49
77
  setupTriggerEvents() {
50
78
  if (!this.hasTriggerTarget) return
51
79
 
@@ -95,27 +123,6 @@ export default class extends RegistryController {
95
123
  if (this.onDocumentClick) document.removeEventListener("click", this.onDocumentClick)
96
124
  }
97
125
 
98
- setupButtonEvents() {
99
- this.handleConfirm = this.confirm.bind(this)
100
- this.handleCancel = this.cancel.bind(this)
101
-
102
- if (this.hasOkButtonTarget) {
103
- this.okButtonTarget.addEventListener("click", this.handleConfirm)
104
- }
105
- if (this.hasCancelButtonTarget) {
106
- this.cancelButtonTarget.addEventListener("click", this.handleCancel)
107
- }
108
- }
109
-
110
- removeButtonEvents() {
111
- if (this.hasOkButtonTarget) {
112
- this.okButtonTarget.removeEventListener("click", this.handleConfirm)
113
- }
114
- if (this.hasCancelButtonTarget) {
115
- this.cancelButtonTarget.removeEventListener("click", this.handleCancel)
116
- }
117
- }
118
-
119
126
  toggle() {
120
127
  this.isOpen ? this.hide() : this.show()
121
128
  }
@@ -152,7 +159,7 @@ export default class extends RegistryController {
152
159
  }
153
160
  }, 300)
154
161
 
155
- this.dispatch("hidden", { prefix: "hakumi-popconfirm" })
162
+ this.dispatch("hidden")
156
163
  }
157
164
 
158
165
  open() { this.show() }
@@ -164,7 +171,6 @@ export default class extends RegistryController {
164
171
  const detail = { api: this.element.hakumiComponent.api, promise: null }
165
172
  const confirmEvent = this.dispatch("confirm", {
166
173
  cancelable: true,
167
- prefix: "hakumi-popconfirm",
168
174
  detail
169
175
  })
170
176
 
@@ -177,7 +183,7 @@ export default class extends RegistryController {
177
183
  this.hide()
178
184
  } catch (error) {
179
185
  console.error("[HakumiComponents] Popconfirm async error:", error)
180
- this.dispatch("confirm-error", { detail: { error }, prefix: "hakumi-popconfirm" })
186
+ this.dispatch("confirmError", { detail: { error } })
181
187
  } finally {
182
188
  this.setLoading(false)
183
189
  }
@@ -188,7 +194,7 @@ export default class extends RegistryController {
188
194
 
189
195
  cancel(e) {
190
196
  if (e) e.stopPropagation()
191
- this.dispatch("cancel", { prefix: "hakumi-popconfirm" })
197
+ this.dispatch("cancel")
192
198
  this.hide()
193
199
  }
194
200
 
@@ -34,8 +34,6 @@ export default class extends RegistryController {
34
34
  singleton: false,
35
35
  api
36
36
  }
37
-
38
- this.#exposeApi(api)
39
37
  }
40
38
 
41
39
  setup() {
@@ -60,11 +58,6 @@ export default class extends RegistryController {
60
58
  }
61
59
  }
62
60
 
63
- disconnect() {
64
- delete this.element.hakumiPopover
65
- super.disconnect()
66
- }
67
-
68
61
  setupTriggerEvents() {
69
62
  if (!this.hasTriggerTarget) return
70
63
 
@@ -163,8 +156,7 @@ export default class extends RegistryController {
163
156
 
164
157
  this.isOpen = false
165
158
  this.updateVisibility()
166
- this.dispatch("hidden", { prefix: "hakumi-popover" })
167
- this.element.dispatchEvent(new CustomEvent("hakumi-component:hidden", { bubbles: true }))
159
+ this.dispatch("hidden")
168
160
  }
169
161
 
170
162
  open() {
@@ -175,8 +167,7 @@ export default class extends RegistryController {
175
167
  close() {
176
168
  this.isOpen = false
177
169
  this.updateVisibility()
178
- this.dispatch("hidden", { prefix: "hakumi-popover" })
179
- this.element.dispatchEvent(new CustomEvent("hakumi-component:hidden", { bubbles: true }))
170
+ this.dispatch("hidden")
180
171
  }
181
172
 
182
173
  toggle() {
@@ -423,18 +414,6 @@ export default class extends RegistryController {
423
414
  return this.hasOpenValue && this.openValue !== undefined
424
415
  }
425
416
 
426
- #exposeApi(api) {
427
- this.element.hakumiPopover = {
428
- open: () => api.open(),
429
- close: () => api.close(),
430
- toggle: () => api.toggle(),
431
- isOpen: () => api.isOpen(),
432
- getState: () => api.getState(),
433
- setPlacement: (placement) => api.setPlacement(placement),
434
- setOpen: (value) => api.setOpen(value)
435
- }
436
- }
437
-
438
417
  #bindOutsideListeners() {
439
418
  if (this.boundOutsideHandlers || !this.triggerModes.includes("click")) return
440
419
 
@@ -44,8 +44,6 @@ export default class extends RegistryController {
44
44
  singleton: false,
45
45
  api
46
46
  }
47
-
48
- this.#exposeApi(api)
49
47
  }
50
48
 
51
49
  setup() {
@@ -56,7 +54,6 @@ export default class extends RegistryController {
56
54
 
57
55
  disconnect() {
58
56
  this.#unwatchThemeChanges()
59
- delete this.element.hakumiQrCode
60
57
  super.disconnect()
61
58
  }
62
59
 
@@ -278,23 +275,6 @@ export default class extends RegistryController {
278
275
  return computed || fallback
279
276
  }
280
277
 
281
- #exposeApi(api) {
282
- this.element.hakumiQrCode = {
283
- getValue: () => api.getValue(),
284
- setValue: (value) => api.setValue(value),
285
- getSize: () => api.getSize(),
286
- setSize: (size) => api.setSize(size),
287
- getType: () => api.getType(),
288
- setType: (type) => api.setType(type),
289
- setColors: (options) => api.setColors(options),
290
- setErrorLevel: (level) => api.setErrorLevel(level),
291
- getDataUrl: () => api.getDataUrl(),
292
- download: (filename) => api.download(filename),
293
- rerender: () => api.rerender(),
294
- getState: () => api.getState()
295
- }
296
- }
297
-
298
278
  #applySizeStyle() {
299
279
  if (!this.sizeValue) return
300
280
  const value = Math.max(1, Number(this.sizeValue))
@@ -44,7 +44,6 @@ export default class extends RegistryController {
44
44
 
45
45
  disconnect() {
46
46
  this._destroyResizeObserver()
47
- delete this.element.hakumiSegmented
48
47
  super.disconnect()
49
48
  }
50
49
 
@@ -84,7 +83,6 @@ export default class extends RegistryController {
84
83
  }
85
84
 
86
85
  #exposeApi(api) {
87
- this.element.hakumiSegmented = api
88
86
  }
89
87
 
90
88
  #updateSelection() {
@@ -28,13 +28,6 @@ export default class extends RegistryController {
28
28
  setTip: (value) => this.setTip(value)
29
29
  }
30
30
  }
31
-
32
- this.#exposeApi()
33
- }
34
-
35
- disconnect() {
36
- delete this.element.hakumiSpin
37
- super.disconnect()
38
31
  }
39
32
 
40
33
  spinningValueChanged() {
@@ -99,7 +92,7 @@ export default class extends RegistryController {
99
92
  this.element.setAttribute("aria-busy", this.spinningValue ? "true" : "false")
100
93
 
101
94
  if (this.visibleSpinning && !value && !silent) {
102
- this.element.dispatchEvent(new CustomEvent("hakumi-component:hidden", { bubbles: true }))
95
+ this.dispatch("hidden")
103
96
  }
104
97
 
105
98
  this.visibleSpinning = value
@@ -116,15 +109,4 @@ export default class extends RegistryController {
116
109
 
117
110
  this.progressElement.setAttribute("aria-valuenow", String(value))
118
111
  }
119
-
120
- #exposeApi() {
121
- this.element.hakumiSpin = {
122
- show: () => this.show(),
123
- hide: () => this.hide(),
124
- toggle: () => this.toggle(),
125
- isSpinning: () => this.spinningValue,
126
- setPercent: (value) => this.setPercent(value),
127
- setTip: (value) => this.setTip(value)
128
- }
129
- }
130
112
  }
@@ -60,7 +60,6 @@ export default class extends RegistryController {
60
60
 
61
61
  disconnect() {
62
62
  this.pause()
63
- delete this.element.hakumiStatistic
64
63
  super.disconnect()
65
64
  }
66
65
 
@@ -244,6 +243,5 @@ export default class extends RegistryController {
244
243
  }
245
244
 
246
245
  #exposeApi(api) {
247
- this.element.hakumiStatistic = api
248
246
  }
249
247
  }
@@ -109,7 +109,6 @@ export default class extends RegistryController {
109
109
  this._columnSortable = null
110
110
  }
111
111
 
112
- delete this.element.hakumiTable
113
112
  super.disconnect()
114
113
  }
115
114
 
@@ -229,7 +228,8 @@ export default class extends RegistryController {
229
228
  }
230
229
 
231
230
  setSelectedRowKeys(keys) {
232
- this._selectedRowKeys = new Set(Array(keys).map(String))
231
+ const keysArray = Array.isArray(keys) ? keys : [keys]
232
+ this._selectedRowKeys = new Set(keysArray.map(String))
233
233
  this.#syncSelection()
234
234
  this.#dispatchSelectionChange()
235
235
  }
@@ -298,7 +298,7 @@ export default class extends RegistryController {
298
298
  }
299
299
 
300
300
  setFilters(filters = {}) {
301
- this._filters = new Map(Object.entries(filters).map(([key, values]) => [key, Array(values)]))
301
+ this._filters = new Map(Object.entries(filters).map(([key, values]) => [key, Array.isArray(values) ? values : [values]]))
302
302
  this.#syncFilterInputs()
303
303
  this.#applyFiltersToRows()
304
304
  }
@@ -308,7 +308,8 @@ export default class extends RegistryController {
308
308
  }
309
309
 
310
310
  setExpandedRowKeys(keys) {
311
- this._expandedRowKeys = new Set(Array(keys).map(String))
311
+ const keysArray = Array.isArray(keys) ? keys : [keys]
312
+ this._expandedRowKeys = new Set(keysArray.map(String))
312
313
  this.#syncExpandedRows()
313
314
  }
314
315
 
@@ -676,35 +677,24 @@ export default class extends RegistryController {
676
677
  })
677
678
  })
678
679
 
679
- this.element.dispatchEvent(
680
- new CustomEvent("hakumi:table:row-editing", {
681
- bubbles: true,
682
- detail: { rowKey, save: saveRow, cancel: cancelRow }
683
- })
684
- )
680
+ this.dispatch("rowEditing", {
681
+ detail: { rowKey, save: saveRow, cancel: cancelRow }
682
+ })
685
683
  }
686
684
 
687
685
  #dispatchEdit(detail) {
688
- this.element.dispatchEvent(
689
- new CustomEvent("hakumi:table:edit", {
690
- bubbles: true,
691
- detail
692
- })
693
- )
686
+ this.dispatch("edit", { detail })
694
687
  }
695
688
 
696
689
  #dispatchRowEdit(detail) {
697
690
  const params = this.#changesToParams(detail.changes)
698
-
699
- this.element.dispatchEvent(
700
- new CustomEvent("hakumi:table:row-edit", {
701
- bubbles: true,
702
- detail: {
703
- ...detail,
704
- params
705
- }
706
- })
707
- )
691
+
692
+ this.dispatch("rowEdit", {
693
+ detail: {
694
+ ...detail,
695
+ params
696
+ }
697
+ })
708
698
  }
709
699
 
710
700
  #changesToParams(changes) {
@@ -735,15 +725,12 @@ export default class extends RegistryController {
735
725
  if (result === false) return false
736
726
 
737
727
  if (typeof result === "string") {
738
- this.element.dispatchEvent(
739
- new CustomEvent("hakumi:table:cell-update-prevented", {
740
- bubbles: true,
741
- detail: {
742
- ...detail,
743
- reason: result
744
- }
745
- })
746
- )
728
+ this.dispatch("cellUpdatePrevented", {
729
+ detail: {
730
+ ...detail,
731
+ reason: result
732
+ }
733
+ })
747
734
  return false
748
735
  }
749
736
 
@@ -773,7 +760,6 @@ export default class extends RegistryController {
773
760
  }
774
761
 
775
762
  #exposeApi(api) {
776
- this.element.hakumiTable = api
777
763
  }
778
764
 
779
765
  #isServerSide() {
@@ -1059,25 +1045,19 @@ export default class extends RegistryController {
1059
1045
  }
1060
1046
 
1061
1047
  #dispatchSelectionChange() {
1062
- this.element.dispatchEvent(
1063
- new CustomEvent("hakumi:table:selection-change", {
1064
- bubbles: true,
1065
- detail: { selectedRowKeys: this.getSelectedRowKeys() }
1066
- })
1067
- )
1048
+ this.dispatch("selectionChange", {
1049
+ detail: { selectedRowKeys: this.getSelectedRowKeys() }
1050
+ })
1068
1051
  }
1069
1052
 
1070
1053
  #dispatchChange() {
1071
- this.element.dispatchEvent(
1072
- new CustomEvent("hakumi:table:change", {
1073
- bubbles: true,
1074
- detail: {
1075
- selectedRowKeys: this.getSelectedRowKeys(),
1076
- sorter: this._sorter,
1077
- filters: this.getFilters()
1078
- }
1079
- })
1080
- )
1054
+ this.dispatch("change", {
1055
+ detail: {
1056
+ selectedRowKeys: this.getSelectedRowKeys(),
1057
+ sorter: this._sorter,
1058
+ filters: this.getFilters()
1059
+ }
1060
+ })
1081
1061
  }
1082
1062
 
1083
1063
  #parseJson(value, fallback) {
@@ -1168,31 +1148,25 @@ export default class extends RegistryController {
1168
1148
  }
1169
1149
 
1170
1150
  #dispatchRowReorder(oldIndex, newIndex, rowKey) {
1171
- this.element.dispatchEvent(
1172
- new CustomEvent("hakumi--table:rowReorder", {
1173
- bubbles: true,
1174
- detail: {
1175
- oldIndex,
1176
- newIndex,
1177
- rowKey,
1178
- order: this.getRowOrder()
1179
- }
1180
- })
1181
- )
1151
+ this.dispatch("rowReorder", {
1152
+ detail: {
1153
+ oldIndex,
1154
+ newIndex,
1155
+ rowKey,
1156
+ order: this.getRowOrder()
1157
+ }
1158
+ })
1182
1159
  }
1183
1160
 
1184
1161
  #dispatchColumnReorder(oldIndex, newIndex, columnKey) {
1185
- this.element.dispatchEvent(
1186
- new CustomEvent("hakumi--table:columnReorder", {
1187
- bubbles: true,
1188
- detail: {
1189
- oldIndex,
1190
- newIndex,
1191
- columnKey,
1192
- order: this.getColumnOrder()
1193
- }
1194
- })
1195
- )
1162
+ this.dispatch("columnReorder", {
1163
+ detail: {
1164
+ oldIndex,
1165
+ newIndex,
1166
+ columnKey,
1167
+ order: this.getColumnOrder()
1168
+ }
1169
+ })
1196
1170
  }
1197
1171
 
1198
1172
  }
@@ -7,16 +7,27 @@ export default class extends Controller {
7
7
  }
8
8
 
9
9
  connect() {
10
- this.#exposeApi()
11
-
10
+ this.element.hakumiComponent = {
11
+ name: "tag",
12
+ version: 1,
13
+ singleton: false,
14
+ api: {
15
+ close: () => this.close(),
16
+ toggle: () => this.toggle(),
17
+ check: () => this.check(),
18
+ uncheck: () => this.uncheck(),
19
+ isChecked: () => this.isChecked()
20
+ }
21
+ }
22
+
12
23
  if (this.checkableValue) {
13
24
  this.element.addEventListener("click", this.#handleClick)
14
25
  }
15
26
  }
16
27
 
17
28
  disconnect() {
18
- delete this.element.hakumiTag
19
-
29
+ delete this.element.hakumiComponent
30
+
20
31
  if (this.checkableValue) {
21
32
  this.element.removeEventListener("click", this.#handleClick)
22
33
  }
@@ -90,14 +101,4 @@ export default class extends Controller {
90
101
  #updateCheckedState() {
91
102
  this.element.classList.toggle("hakumi-tag-checkable-checked", this.checkedValue)
92
103
  }
93
-
94
- #exposeApi() {
95
- this.element.hakumiTag = {
96
- close: () => this.close(),
97
- toggle: () => this.toggle(),
98
- check: () => this.check(),
99
- uncheck: () => this.uncheck(),
100
- isChecked: () => this.isChecked()
101
- }
102
- }
103
104
  }
@@ -9,17 +9,28 @@ export default class extends Controller {
9
9
 
10
10
  connect() {
11
11
  this._sortable = null
12
-
12
+
13
13
  if (this.sortableValue) {
14
14
  this.#setupSortable()
15
15
  }
16
16
 
17
- this.#exposeApi()
17
+ this.element.hakumiComponent = {
18
+ name: "tag_group",
19
+ version: 1,
20
+ singleton: false,
21
+ api: {
22
+ enableSorting: () => this.enableSorting(),
23
+ disableSorting: () => this.disableSorting(),
24
+ isSortingEnabled: () => this.isSortingEnabled(),
25
+ getTags: () => this.getTags(),
26
+ getTagValues: () => this.getTagValues()
27
+ }
28
+ }
18
29
  }
19
30
 
20
31
  disconnect() {
21
32
  this.#destroySortable()
22
- delete this.element.hakumiTagGroup
33
+ delete this.element.hakumiComponent
23
34
  }
24
35
 
25
36
  sortableValueChanged() {
@@ -81,14 +92,4 @@ export default class extends Controller {
81
92
  this._sortable = null
82
93
  }
83
94
  }
84
-
85
- #exposeApi() {
86
- this.element.hakumiTagGroup = {
87
- enableSorting: () => this.enableSorting(),
88
- disableSorting: () => this.disableSorting(),
89
- isSortingEnabled: () => this.isSortingEnabled(),
90
- getTags: () => this.getTags(),
91
- getTagValues: () => this.getTagValues()
92
- }
93
- }
94
95
  }
@@ -20,7 +20,21 @@ export default class extends RegistryController {
20
20
 
21
21
  this._mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
22
22
 
23
+ // Check if there's an existing explicit theme set in HTML
24
+ const existingTheme = document.documentElement.getAttribute("data-theme")
25
+ const shouldAutoDetect = !existingTheme || existingTheme === "auto"
26
+
23
27
  this._handleSystemThemeChange = (e) => {
28
+ const currentTheme = document.documentElement.getAttribute("data-theme")
29
+ // If theme is "auto", don't change the attribute - let CSS media query handle it
30
+ // Only dispatch event for sync purposes
31
+ if (currentTheme === "auto") {
32
+ const effectiveTheme = e.matches ? "dark" : "light"
33
+ window.dispatchEvent(new CustomEvent("theme-changed", { detail: { theme: effectiveTheme } }))
34
+ if (this.syncSandboxValue) this.syncSandboxFrames(effectiveTheme)
35
+ return
36
+ }
37
+ // For non-auto themes, only update if no stored preference
24
38
  if (!Persistence.storage.get(this.storageKeyValue)) {
25
39
  this.setTheme(e.matches ? "dark" : "light")
26
40
  }
@@ -34,7 +48,16 @@ export default class extends RegistryController {
34
48
  })
35
49
  }
36
50
 
37
- this.setTheme(this.detectInitialTheme())
51
+ // Only set theme if there's no explicit theme
52
+ // If "auto", don't change the attribute - CSS media query handles it
53
+ if (!existingTheme) {
54
+ this.setTheme(this.detectInitialTheme())
55
+ } else if (existingTheme === "auto") {
56
+ // For "auto", just dispatch event and sync sandboxes without changing attribute
57
+ const effectiveTheme = this._mediaQuery.matches ? "dark" : "light"
58
+ window.dispatchEvent(new CustomEvent("theme-changed", { detail: { theme: effectiveTheme } }))
59
+ if (this.syncSandboxValue) this.syncSandboxFrames(effectiveTheme)
60
+ }
38
61
  }
39
62
 
40
63
  teardown() {
@@ -270,8 +270,7 @@ export default class extends RegistryController {
270
270
  }
271
271
 
272
272
  dispatchChange(value, parts) {
273
- this.element.dispatchEvent(new CustomEvent("hakumi:time-picker:change", {
274
- bubbles: true,
273
+ this.dispatch("change", {
275
274
  detail: {
276
275
  value,
277
276
  hour: parts.hour,
@@ -279,13 +278,10 @@ export default class extends RegistryController {
279
278
  second: parts.second,
280
279
  period: parts.period
281
280
  }
282
- }))
281
+ })
283
282
  }
284
283
 
285
284
  dispatchOpenChange(open) {
286
- this.element.dispatchEvent(new CustomEvent("hakumi:time-picker:open-change", {
287
- bubbles: true,
288
- detail: { open }
289
- }))
285
+ this.dispatch("openChange", { detail: { open } })
290
286
  }
291
287
  }
@@ -22,13 +22,6 @@ export default class extends RegistryController {
22
22
  toggleReverse: () => this.toggleReverse()
23
23
  }
24
24
  }
25
-
26
- this.#exposeApi()
27
- }
28
-
29
- disconnect() {
30
- delete this.element.hakumiTimeline
31
- super.disconnect()
32
25
  }
33
26
 
34
27
  setReverse(value) {
@@ -76,13 +69,4 @@ export default class extends RegistryController {
76
69
 
77
70
  ordered.forEach((item) => this.itemsContainer.appendChild(item))
78
71
  }
79
-
80
- #exposeApi() {
81
- this.element.hakumiTimeline = {
82
- getItems: () => this.getItems(),
83
- getReverse: () => this.reverseValue,
84
- setReverse: (value) => this.setReverse(value),
85
- toggleReverse: () => this.toggleReverse()
86
- }
87
- }
88
72
  }