juxscript 1.1.240 → 1.1.244

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 (297) hide show
  1. package/index.js +7 -137
  2. package/lib/components/dataframe.ts +0 -569
  3. package/lib/components/include.ts +229 -229
  4. package/lib/components/tag.ts +68 -0
  5. package/lib/styles/shadcn.css +17 -5
  6. package/lib/utils/idgen.ts +6 -0
  7. package/package.json +5 -6
  8. package/index.d.ts +0 -239
  9. package/index.d.ts.map +0 -1
  10. package/lib/components/alert.d.ts +0 -36
  11. package/lib/components/alert.d.ts.map +0 -1
  12. package/lib/components/alert.js +0 -172
  13. package/lib/components/alert.ts +0 -219
  14. package/lib/components/app.d.ts +0 -89
  15. package/lib/components/app.d.ts.map +0 -1
  16. package/lib/components/app.js +0 -175
  17. package/lib/components/app.ts +0 -247
  18. package/lib/components/badge.d.ts +0 -26
  19. package/lib/components/badge.d.ts.map +0 -1
  20. package/lib/components/badge.js +0 -91
  21. package/lib/components/badge.ts +0 -118
  22. package/lib/components/base/Animations.d.ts +0 -36
  23. package/lib/components/base/Animations.d.ts.map +0 -1
  24. package/lib/components/base/Animations.js +0 -70
  25. package/lib/components/base/Animations.ts +0 -112
  26. package/lib/components/base/BaseComponent.d.ts +0 -294
  27. package/lib/components/base/BaseComponent.d.ts.map +0 -1
  28. package/lib/components/base/BaseComponent.js +0 -735
  29. package/lib/components/base/BaseComponent.ts +0 -884
  30. package/lib/components/base/FormInput.d.ts +0 -77
  31. package/lib/components/base/FormInput.d.ts.map +0 -1
  32. package/lib/components/base/FormInput.js +0 -171
  33. package/lib/components/base/FormInput.ts +0 -237
  34. package/lib/components/blueprint.d.ts +0 -40
  35. package/lib/components/blueprint.d.ts.map +0 -1
  36. package/lib/components/blueprint.js +0 -327
  37. package/lib/components/button.d.ts +0 -70
  38. package/lib/components/button.d.ts.map +0 -1
  39. package/lib/components/button.js +0 -177
  40. package/lib/components/button.ts +0 -237
  41. package/lib/components/card.d.ts +0 -35
  42. package/lib/components/card.d.ts.map +0 -1
  43. package/lib/components/card.js +0 -130
  44. package/lib/components/card.ts +0 -177
  45. package/lib/components/chart.d.ts +0 -49
  46. package/lib/components/chart.d.ts.map +0 -1
  47. package/lib/components/chart.js +0 -205
  48. package/lib/components/chart.ts +0 -254
  49. package/lib/components/checkbox.d.ts +0 -33
  50. package/lib/components/checkbox.d.ts.map +0 -1
  51. package/lib/components/checkbox.js +0 -202
  52. package/lib/components/checkbox.ts +0 -260
  53. package/lib/components/code.d.ts +0 -52
  54. package/lib/components/code.d.ts.map +0 -1
  55. package/lib/components/code.js +0 -201
  56. package/lib/components/code.ts +0 -260
  57. package/lib/components/container.d.ts +0 -60
  58. package/lib/components/container.d.ts.map +0 -1
  59. package/lib/components/container.js +0 -195
  60. package/lib/components/container.ts +0 -259
  61. package/lib/components/data.d.ts +0 -36
  62. package/lib/components/data.d.ts.map +0 -1
  63. package/lib/components/data.js +0 -110
  64. package/lib/components/data.ts +0 -135
  65. package/lib/components/dataframe/DataFrameSource.d.ts +0 -118
  66. package/lib/components/dataframe/DataFrameSource.d.ts.map +0 -1
  67. package/lib/components/dataframe/DataFrameSource.js +0 -421
  68. package/lib/components/dataframe/DataFrameSource.ts +0 -532
  69. package/lib/components/dataframe/ImportSettingsModal.d.ts +0 -60
  70. package/lib/components/dataframe/ImportSettingsModal.d.ts.map +0 -1
  71. package/lib/components/dataframe/ImportSettingsModal.js +0 -442
  72. package/lib/components/dataframe/ImportSettingsModal.ts +0 -531
  73. package/lib/components/dataframe.d.ts +0 -110
  74. package/lib/components/dataframe.d.ts.map +0 -1
  75. package/lib/components/dataframe.js +0 -470
  76. package/lib/components/datepicker.d.ts +0 -40
  77. package/lib/components/datepicker.d.ts.map +0 -1
  78. package/lib/components/datepicker.js +0 -193
  79. package/lib/components/datepicker.ts +0 -251
  80. package/lib/components/dialog.d.ts +0 -39
  81. package/lib/components/dialog.d.ts.map +0 -1
  82. package/lib/components/dialog.js +0 -131
  83. package/lib/components/dialog.ts +0 -178
  84. package/lib/components/divider.d.ts +0 -31
  85. package/lib/components/divider.d.ts.map +0 -1
  86. package/lib/components/divider.js +0 -72
  87. package/lib/components/divider.ts +0 -104
  88. package/lib/components/dropdown-menu.d.ts +0 -42
  89. package/lib/components/dropdown-menu.d.ts.map +0 -1
  90. package/lib/components/dropdown-menu.js +0 -177
  91. package/lib/components/dropdown-menu.ts +0 -214
  92. package/lib/components/dropdown.d.ts +0 -41
  93. package/lib/components/dropdown.d.ts.map +0 -1
  94. package/lib/components/dropdown.js +0 -136
  95. package/lib/components/dropdown.ts +0 -188
  96. package/lib/components/element.d.ts +0 -51
  97. package/lib/components/element.d.ts.map +0 -1
  98. package/lib/components/element.js +0 -209
  99. package/lib/components/element.ts +0 -271
  100. package/lib/components/event-chain.d.ts +0 -9
  101. package/lib/components/event-chain.d.ts.map +0 -1
  102. package/lib/components/event-chain.js +0 -33
  103. package/lib/components/fileupload.d.ts +0 -98
  104. package/lib/components/fileupload.d.ts.map +0 -1
  105. package/lib/components/fileupload.js +0 -351
  106. package/lib/components/fileupload.ts +0 -449
  107. package/lib/components/grid.d.ts +0 -88
  108. package/lib/components/grid.d.ts.map +0 -1
  109. package/lib/components/grid.js +0 -208
  110. package/lib/components/grid.ts +0 -295
  111. package/lib/components/heading.d.ts +0 -25
  112. package/lib/components/heading.d.ts.map +0 -1
  113. package/lib/components/heading.js +0 -83
  114. package/lib/components/heading.ts +0 -113
  115. package/lib/components/helpers.d.ts +0 -9
  116. package/lib/components/helpers.d.ts.map +0 -1
  117. package/lib/components/helpers.js +0 -30
  118. package/lib/components/helpers.ts +0 -41
  119. package/lib/components/hero.d.ts +0 -60
  120. package/lib/components/hero.d.ts.map +0 -1
  121. package/lib/components/hero.js +0 -239
  122. package/lib/components/hero.ts +0 -302
  123. package/lib/components/history/StateHistory.d.ts +0 -91
  124. package/lib/components/history/StateHistory.d.ts.map +0 -1
  125. package/lib/components/history/StateHistory.js +0 -154
  126. package/lib/components/history/StateHistory.ts +0 -200
  127. package/lib/components/icon.d.ts +0 -36
  128. package/lib/components/icon.d.ts.map +0 -1
  129. package/lib/components/icon.js +0 -135
  130. package/lib/components/icon.ts +0 -182
  131. package/lib/components/icons.d.ts +0 -25
  132. package/lib/components/icons.d.ts.map +0 -1
  133. package/lib/components/icons.js +0 -440
  134. package/lib/components/icons.ts +0 -464
  135. package/lib/components/image.d.ts +0 -42
  136. package/lib/components/image.d.ts.map +0 -1
  137. package/lib/components/image.js +0 -204
  138. package/lib/components/image.ts +0 -260
  139. package/lib/components/include.d.ts +0 -86
  140. package/lib/components/include.d.ts.map +0 -1
  141. package/lib/components/include.js +0 -238
  142. package/lib/components/input.d.ts +0 -85
  143. package/lib/components/input.d.ts.map +0 -1
  144. package/lib/components/input.js +0 -362
  145. package/lib/components/input.ts +0 -473
  146. package/lib/components/layer.d.ts +0 -72
  147. package/lib/components/layer.d.ts.map +0 -1
  148. package/lib/components/layer.js +0 -219
  149. package/lib/components/layer.ts +0 -304
  150. package/lib/components/link.d.ts +0 -41
  151. package/lib/components/link.d.ts.map +0 -1
  152. package/lib/components/link.js +0 -216
  153. package/lib/components/link.ts +0 -268
  154. package/lib/components/list.d.ts +0 -83
  155. package/lib/components/list.d.ts.map +0 -1
  156. package/lib/components/list.js +0 -314
  157. package/lib/components/list.ts +0 -423
  158. package/lib/components/loading.d.ts +0 -25
  159. package/lib/components/loading.d.ts.map +0 -1
  160. package/lib/components/loading.js +0 -76
  161. package/lib/components/loading.ts +0 -104
  162. package/lib/components/menu.d.ts +0 -38
  163. package/lib/components/menu.d.ts.map +0 -1
  164. package/lib/components/menu.js +0 -205
  165. package/lib/components/menu.ts +0 -279
  166. package/lib/components/modal.d.ts +0 -97
  167. package/lib/components/modal.d.ts.map +0 -1
  168. package/lib/components/modal.js +0 -463
  169. package/lib/components/modal.ts +0 -576
  170. package/lib/components/nav.d.ts +0 -46
  171. package/lib/components/nav.d.ts.map +0 -1
  172. package/lib/components/nav.js +0 -193
  173. package/lib/components/nav.ts +0 -261
  174. package/lib/components/paragraph.d.ts +0 -30
  175. package/lib/components/paragraph.d.ts.map +0 -1
  176. package/lib/components/paragraph.js +0 -93
  177. package/lib/components/paragraph.ts +0 -123
  178. package/lib/components/pen.d.ts +0 -125
  179. package/lib/components/pen.d.ts.map +0 -1
  180. package/lib/components/pen.js +0 -443
  181. package/lib/components/pen.ts +0 -567
  182. package/lib/components/progress.d.ts +0 -40
  183. package/lib/components/progress.d.ts.map +0 -1
  184. package/lib/components/progress.js +0 -116
  185. package/lib/components/progress.ts +0 -163
  186. package/lib/components/radio.d.ts +0 -43
  187. package/lib/components/radio.d.ts.map +0 -1
  188. package/lib/components/radio.js +0 -226
  189. package/lib/components/radio.ts +0 -303
  190. package/lib/components/registry.d.ts +0 -34
  191. package/lib/components/registry.d.ts.map +0 -1
  192. package/lib/components/registry.js +0 -163
  193. package/lib/components/registry.ts +0 -193
  194. package/lib/components/req.d.ts +0 -155
  195. package/lib/components/req.d.ts.map +0 -1
  196. package/lib/components/req.js +0 -253
  197. package/lib/components/req.ts +0 -303
  198. package/lib/components/script.d.ts +0 -14
  199. package/lib/components/script.d.ts.map +0 -1
  200. package/lib/components/script.js +0 -33
  201. package/lib/components/script.ts +0 -41
  202. package/lib/components/select.d.ts +0 -42
  203. package/lib/components/select.d.ts.map +0 -1
  204. package/lib/components/select.js +0 -209
  205. package/lib/components/select.ts +0 -281
  206. package/lib/components/sidebar.d.ts +0 -59
  207. package/lib/components/sidebar.d.ts.map +0 -1
  208. package/lib/components/sidebar.js +0 -298
  209. package/lib/components/sidebar.ts +0 -395
  210. package/lib/components/stack/BaseStack.d.ts +0 -65
  211. package/lib/components/stack/BaseStack.d.ts.map +0 -1
  212. package/lib/components/stack/BaseStack.js +0 -274
  213. package/lib/components/stack/BaseStack.ts +0 -328
  214. package/lib/components/stack/HStack.d.ts +0 -18
  215. package/lib/components/stack/HStack.d.ts.map +0 -1
  216. package/lib/components/stack/HStack.js +0 -22
  217. package/lib/components/stack/HStack.ts +0 -25
  218. package/lib/components/stack/VStack.d.ts +0 -19
  219. package/lib/components/stack/VStack.d.ts.map +0 -1
  220. package/lib/components/stack/VStack.js +0 -23
  221. package/lib/components/stack/VStack.ts +0 -26
  222. package/lib/components/stack/ZStack.d.ts +0 -18
  223. package/lib/components/stack/ZStack.d.ts.map +0 -1
  224. package/lib/components/stack/ZStack.js +0 -22
  225. package/lib/components/stack/ZStack.ts +0 -25
  226. package/lib/components/style.d.ts +0 -14
  227. package/lib/components/style.d.ts.map +0 -1
  228. package/lib/components/style.js +0 -33
  229. package/lib/components/style.ts +0 -41
  230. package/lib/components/switch.d.ts +0 -34
  231. package/lib/components/switch.d.ts.map +0 -1
  232. package/lib/components/switch.js +0 -209
  233. package/lib/components/switch.ts +0 -272
  234. package/lib/components/table.d.ts +0 -137
  235. package/lib/components/table.d.ts.map +0 -1
  236. package/lib/components/table.js +0 -1019
  237. package/lib/components/table.ts +0 -1225
  238. package/lib/components/tabs.d.ts +0 -53
  239. package/lib/components/tabs.d.ts.map +0 -1
  240. package/lib/components/tabs.js +0 -275
  241. package/lib/components/tabs.ts +0 -349
  242. package/lib/components/theme-toggle.d.ts +0 -45
  243. package/lib/components/theme-toggle.d.ts.map +0 -1
  244. package/lib/components/theme-toggle.js +0 -218
  245. package/lib/components/theme-toggle.ts +0 -297
  246. package/lib/components/tooltip.d.ts +0 -31
  247. package/lib/components/tooltip.d.ts.map +0 -1
  248. package/lib/components/tooltip.js +0 -112
  249. package/lib/components/tooltip.ts +0 -148
  250. package/lib/components/watcher.d.ts +0 -195
  251. package/lib/components/watcher.d.ts.map +0 -1
  252. package/lib/components/watcher.js +0 -241
  253. package/lib/components/watcher.ts +0 -261
  254. package/lib/components/write.d.ts +0 -107
  255. package/lib/components/write.d.ts.map +0 -1
  256. package/lib/components/write.js +0 -222
  257. package/lib/components/write.ts +0 -272
  258. package/lib/data/DataPipeline.d.ts +0 -113
  259. package/lib/data/DataPipeline.d.ts.map +0 -1
  260. package/lib/data/DataPipeline.js +0 -359
  261. package/lib/data/DataPipeline.ts +0 -452
  262. package/lib/facades/dataframe.jux +0 -0
  263. package/lib/globals.d.ts +0 -21
  264. package/lib/reactivity/state.d.ts +0 -36
  265. package/lib/reactivity/state.d.ts.map +0 -1
  266. package/lib/reactivity/state.js +0 -67
  267. package/lib/reactivity/state.ts +0 -78
  268. package/lib/storage/DataFrame.d.ts +0 -284
  269. package/lib/storage/DataFrame.d.ts.map +0 -1
  270. package/lib/storage/DataFrame.js +0 -1022
  271. package/lib/storage/DataFrame.ts +0 -1195
  272. package/lib/storage/DataFrameSource.d.ts +0 -158
  273. package/lib/storage/DataFrameSource.d.ts.map +0 -1
  274. package/lib/storage/DataFrameSource.js +0 -409
  275. package/lib/storage/DataFrameSource.ts +0 -556
  276. package/lib/storage/FileStorage.d.ts +0 -53
  277. package/lib/storage/FileStorage.d.ts.map +0 -1
  278. package/lib/storage/FileStorage.js +0 -80
  279. package/lib/storage/FileStorage.ts +0 -95
  280. package/lib/storage/IndexedDBDriver.d.ts +0 -75
  281. package/lib/storage/IndexedDBDriver.d.ts.map +0 -1
  282. package/lib/storage/IndexedDBDriver.js +0 -177
  283. package/lib/storage/IndexedDBDriver.ts +0 -226
  284. package/lib/storage/TabularDriver.d.ts +0 -118
  285. package/lib/storage/TabularDriver.d.ts.map +0 -1
  286. package/lib/storage/TabularDriver.js +0 -731
  287. package/lib/storage/TabularDriver.ts +0 -874
  288. package/lib/utils/codeparser.d.ts +0 -29
  289. package/lib/utils/codeparser.d.ts.map +0 -1
  290. package/lib/utils/codeparser.js +0 -409
  291. package/lib/utils/fetch.d.ts +0 -176
  292. package/lib/utils/fetch.d.ts.map +0 -1
  293. package/lib/utils/fetch.js +0 -427
  294. package/lib/utils/formatId.d.ts +0 -16
  295. package/lib/utils/formatId.d.ts.map +0 -1
  296. package/lib/utils/formatId.js +0 -27
  297. package/lib/utils/path-resolver.js +0 -23
@@ -1,884 +0,0 @@
1
- import { State } from '../../reactivity/state.js';
2
- import { getOrCreateContainer } from '../helpers.js';
3
- import { registry } from '../registry.js';
4
- import { stateHistory } from '../history/StateHistory.js';
5
- import { formatIdAsLabel } from '../../utils/formatId.js';
6
- import { applyAnimations, Animations } from './Animations.js'; // ✅ Import Animations
7
-
8
- /**
9
- * Base state interface that ALL component states must extend
10
- */
11
- export interface BaseState {
12
- visible?: boolean;
13
- disabled?: boolean;
14
- loading?: boolean;
15
- class?: string;
16
- style?: string;
17
- attributes?: Record<string, string>;
18
- // ✅ Form-specific properties (optional for all components)
19
- label?: string;
20
- required?: boolean;
21
- name?: string;
22
- errorMessage?: string;
23
- }
24
-
25
- /**
26
- * Abstract base class for all JUX components
27
- * Provides common storage, event routing, and lifecycle methods
28
- *
29
- * Children must provide:
30
- * - TRIGGER_EVENTS constant (readonly string[])
31
- * - CALLBACK_EVENTS constant (readonly string[])
32
- * - render() implementation
33
- */
34
- export abstract class BaseComponent<TState extends BaseState = BaseState> {
35
- // Common properties (all components have these)
36
- state: TState;
37
- container: HTMLElement | null = null;
38
- _id: string;
39
- id: string;
40
-
41
- // Event & sync storage (populated by bind() and sync())
42
- protected _bindings: Array<{ event: string, handler: Function }> = [];
43
- protected _syncBindings: Array<{
44
- property: string,
45
- stateObj: State<any>,
46
- toState?: Function,
47
- toComponent?: Function
48
- }> = [];
49
- protected _triggerHandlers: Map<string, Function> = new Map();
50
- protected _callbackHandlers: Map<string, Function> = new Map();
51
- protected _isUpdatingSync: boolean = false;
52
-
53
- // Form-specific protected properties (only used by form components)
54
- protected _inputElement: HTMLElement | null = null;
55
- protected _labelElement: HTMLLabelElement | null = null;
56
- protected _errorElement: HTMLElement | null = null;
57
- protected _onValidate?: (value: any) => boolean | string;
58
- protected _hasBeenValidated: boolean = false;
59
-
60
- constructor(id: string, initialState: TState) {
61
- this._id = id;
62
- this.id = id;
63
-
64
- const stateWithDefaults: TState = {
65
- visible: true,
66
- disabled: false,
67
- loading: false,
68
- class: '',
69
- style: '',
70
- attributes: {},
71
- ...initialState
72
- };
73
-
74
- this.state = new Proxy(stateWithDefaults, {
75
- set: (target, prop, value) => {
76
- const key = prop as keyof TState;
77
- const oldValue = target[key];
78
-
79
- if (oldValue !== value) {
80
- stateHistory.recordStateChange(this._id, prop as string, oldValue, value);
81
- target[key] = value;
82
- this.update(prop as string, value);
83
-
84
- if (!this._isUpdatingSync) {
85
- this._notifySyncedState(prop as string, value);
86
- }
87
- }
88
- return true;
89
- }
90
- });
91
-
92
- // ✅ Apply ONLY animation extensions
93
- applyAnimations(this);
94
- }
95
-
96
- /* ═════════════════════════════════════════════════════════════════
97
- * ABSTRACT METHODS (Child must implement)
98
- * ═════════════════════════════════════════════════════════════════ */
99
-
100
- protected abstract getTriggerEvents(): readonly string[];
101
- protected abstract getCallbackEvents(): readonly string[];
102
- abstract render(targetId?: string | HTMLElement | BaseComponent<any>): this;
103
-
104
- /**
105
- * REACTIVE UPDATE HOOK (PUBLIC, CONCRETE)
106
- * Called automatically when this.state[prop] changes via Proxy.
107
- * Default implementation handles base properties.
108
- * Children can override to add component-specific logic.
109
- */
110
- update(prop: string, value: any): void {
111
- if (!this.container) return;
112
-
113
- const el = this.container.querySelector(`#${this._id}`) as HTMLElement;
114
- if (!el) return;
115
-
116
- // Handle base properties
117
- switch (prop) {
118
- case 'visible':
119
- el.style.display = value ? '' : 'none';
120
- break;
121
-
122
- case 'class':
123
- const baseClasses = el.className.split(' ').filter(c => c.startsWith('jux-'));
124
- const userClasses = value.split(' ').filter((c: string) => c);
125
- el.className = [...baseClasses, ...userClasses].join(' ');
126
- break;
127
-
128
- case 'style':
129
- el.setAttribute('style', value);
130
- break;
131
-
132
- case 'disabled':
133
- el.setAttribute('aria-disabled', String(value));
134
- const inputs = el.querySelectorAll('input, button, select, textarea');
135
- inputs.forEach(input => {
136
- (input as HTMLInputElement).disabled = value;
137
- });
138
- break;
139
-
140
- case 'loading':
141
- el.classList.toggle('jux-loading', value);
142
- el.setAttribute('aria-busy', String(value));
143
- break;
144
- }
145
- }
146
-
147
- /**
148
- * ✨ Notify external State<T> objects when component state changes
149
- * Prevents infinite loops with guard flag
150
- */
151
- protected _notifySyncedState(prop: string, value: any): void {
152
- const syncBinding = this._syncBindings.find(b => b.property === prop);
153
-
154
- if (syncBinding) {
155
- const { stateObj, toState } = syncBinding;
156
-
157
- // Set guard flag to prevent recursion
158
- this._isUpdatingSync = true;
159
-
160
- try {
161
- const transformedValue = toState ? toState(value) : value;
162
- stateObj.set(transformedValue);
163
- } finally {
164
- // Always clear guard flag
165
- this._isUpdatingSync = false;
166
- }
167
- }
168
- }
169
-
170
- /* ═════════════════════════════════════════════════════════════════
171
- * COMMON FLUENT API (Inherited by all components)
172
- * ═════════════════════════════════════════════════════════════════ */
173
-
174
- /**
175
- * Set component style
176
- */
177
- style(value: string): this {
178
- this.state.style = value; // ✅ Triggers update()
179
- return this;
180
- }
181
-
182
- /**
183
- * Set component class
184
- */
185
- class(value: string): this {
186
- this.state.class = value; // ✅ Triggers update()
187
- return this;
188
- }
189
-
190
- /* ═════════════════════════════════════════════════════════════════
191
- * CSS CLASS MANAGEMENT
192
- * ═════════════════════════════════════════════════════════════════ */
193
-
194
- /**
195
- * Add a CSS class to the component
196
- */
197
- addClass(value: string): this {
198
- const current = this.state.class || '';
199
- const classes = current.split(' ').filter((c: string) => c && c !== value);
200
- classes.push(value);
201
- this.state.class = classes.join(' '); // ✅ Triggers update()
202
- return this;
203
- }
204
-
205
- /**
206
- * Remove a CSS class from the component
207
- */
208
- removeClass(value: string): this {
209
- const current = this.state.class || '';
210
- const classes = current.split(' ').filter((c: string) => c && c !== value);
211
- this.state.class = classes.join(' '); // ✅ Triggers update()
212
- return this;
213
- }
214
-
215
- /**
216
- * Toggle a CSS class on the component
217
- */
218
- toggleClass(value: string): this {
219
- const current = this.state.class || '';
220
- const hasClass = current.split(' ').includes(value);
221
- return hasClass ? this.removeClass(value) : this.addClass(value);
222
- }
223
-
224
- /* ═════════════════════════════════════════════════════════════════
225
- * VISIBILITY CONTROL
226
- * ═════════════════════════════════════════════════════════════════ */
227
-
228
- /**
229
- * Set component visibility
230
- */
231
- visible(value: boolean): this {
232
- this.state.visible = value; // ✅ Triggers update()
233
- return this;
234
- }
235
-
236
- /**
237
- * Show the component
238
- */
239
- show(): this {
240
- return this.visible(true);
241
- }
242
-
243
- /**
244
- * Hide the component
245
- */
246
- hide(): this {
247
- return this.visible(false);
248
- }
249
-
250
- /**
251
- * Toggle component visibility
252
- */
253
- toggleVisibility(): this {
254
- const isVisible = (this.state as any).visible ?? true;
255
- return this.visible(!isVisible);
256
- }
257
-
258
- /* ═════════════════════════════════════════════════════════════════
259
- * ATTRIBUTE MANAGEMENT
260
- * ═════════════════════════════════════════════════════════════════ */
261
-
262
- /**
263
- * Set a single HTML attribute
264
- */
265
- attr(name: string, value: string): this {
266
- const attrs = (this.state as any).attributes || {};
267
- (this.state as any).attributes = { ...attrs, [name]: value };
268
- if (this.container) this.container.setAttribute(name, value);
269
- return this;
270
- }
271
-
272
- /**
273
- * Set multiple HTML attributes
274
- */
275
- attrs(attributes: Record<string, string>): this {
276
- Object.entries(attributes).forEach(([name, value]) => {
277
- this.attr(name, value);
278
- });
279
- return this;
280
- }
281
-
282
- /**
283
- * Remove an HTML attribute
284
- */
285
- removeAttr(name: string): this {
286
- const attrs = (this.state as any).attributes || {};
287
- delete attrs[name];
288
- if (this.container) this.container.removeAttribute(name);
289
- return this;
290
- }
291
-
292
- /* ═════════════════════════════════════════════════════════════════
293
- * DISABLED STATE
294
- * ═════════════════════════════════════════════════════════════════ */
295
-
296
- /**
297
- * Set disabled state for interactive elements
298
- */
299
- disabled(value: boolean): this {
300
- this.state.disabled = value; // ✅ Triggers update()
301
- return this;
302
- }
303
-
304
- /**
305
- * Enable the component
306
- */
307
- enable(): this {
308
- return this.disabled(false);
309
- }
310
-
311
- /**
312
- * Disable the component
313
- */
314
- disable(): this {
315
- return this.disabled(true);
316
- }
317
-
318
- /* ═════════════════════════════════════════════════════════════════
319
- * LOADING STATE
320
- * ═════════════════════════════════════════════════════════════════ */
321
-
322
- /**
323
- * Set loading state
324
- */
325
- loading(value: boolean): this {
326
- this.state.loading = value; // ✅ Triggers update()
327
- return this;
328
- }
329
-
330
- /* ═════════════════════════════════════════════════════════════════
331
- * FOCUS MANAGEMENT
332
- * ═════════════════════════════════════════════════════════════════ */
333
-
334
- /**
335
- * Focus the first focusable element in the component
336
- */
337
- focus(): this {
338
- if (this.container) {
339
- const focusable = this.container.querySelector('input, button, select, textarea, [tabindex]');
340
- if (focusable) (focusable as HTMLElement).focus();
341
- }
342
- return this;
343
- }
344
-
345
- /**
346
- * Blur the currently focused element in the component
347
- */
348
- blur(): this {
349
- if (this.container) {
350
- const focused = this.container.querySelector(':focus');
351
- if (focused) (focused as HTMLElement).blur();
352
- }
353
- return this;
354
- }
355
-
356
- /* ═════════════════════════════════════════════════════════════════
357
- * DOM MANIPULATION
358
- * ═════════════════════════════════════════════════════════════════ */
359
-
360
- /**
361
- * Remove the component from the DOM
362
- */
363
- remove(): this {
364
- if (this.container) {
365
- this.container.remove();
366
- this.container = null;
367
-
368
- // ✅ Unregister when removed
369
- registry.unregister(this._id);
370
- }
371
- return this;
372
- }
373
-
374
- /* ═════════════════════════════════════════════════════════════════
375
- * EVENT BINDING (Shared logic)
376
- * ═════════════════════════════════════════════════════════════════ */
377
-
378
- bind(event: string, handler: Function): this {
379
- // ✅ Record bind event
380
- stateHistory.recordEvent(this._id, 'bind', event, { handlerName: handler.name });
381
-
382
- if (this._isTriggerEvent(event)) {
383
- this._triggerHandlers.set(event, handler);
384
- } else if (this._isCallbackEvent(event)) {
385
- this._callbackHandlers.set(event, handler);
386
- } else {
387
- this._bindings.push({ event, handler });
388
- }
389
- return this;
390
- }
391
-
392
- sync(property: string, stateObj: State<any>, toStateOrTransform?: Function, toComponent?: Function): this {
393
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
394
- throw new Error(`${this.constructor.name}.sync: Expected a State object for property "${property}"`);
395
- }
396
-
397
- const actualToState = (toComponent !== undefined) ? toStateOrTransform : undefined;
398
- const actualToComponent = (toComponent !== undefined) ? toComponent : toStateOrTransform;
399
-
400
- // ✅ Record sync event
401
- stateHistory.recordEvent(this._id, 'sync', property, {
402
- hasToState: !!actualToState,
403
- hasToComponent: !!actualToComponent
404
- });
405
-
406
- this._syncBindings.push({
407
- property,
408
- stateObj,
409
- toState: actualToState,
410
- toComponent: actualToComponent
411
- });
412
- return this;
413
- }
414
-
415
- protected _isTriggerEvent(event: string): boolean {
416
- return this.getTriggerEvents().includes(event);
417
- }
418
-
419
- protected _isCallbackEvent(event: string): boolean {
420
- return this.getCallbackEvents().includes(event);
421
- }
422
-
423
- protected _triggerCallback(eventName: string, ...args: any[]): void {
424
- // ✅ Record callback event
425
- stateHistory.recordEvent(this._id, 'callback', eventName, { argsCount: args.length });
426
-
427
- if (this._callbackHandlers.has(eventName)) {
428
- const handler = this._callbackHandlers.get(eventName)!;
429
- handler(...args);
430
- }
431
- }
432
-
433
- /* ═════════════════════════════════════════════════════════════════
434
- * FLUENT EVENT SHORTHANDS
435
- * ═════════════════════════════════════════════════════════════════ */
436
-
437
- /**
438
- * Register a click event handler
439
- */
440
- click(handler: (e: MouseEvent) => void): this {
441
- return this.bind('click', handler);
442
- }
443
-
444
- /**
445
- * Register a double-click event handler
446
- */
447
- dblclick(handler: (e: MouseEvent) => void): this {
448
- return this.bind('dblclick', handler);
449
- }
450
-
451
- /**
452
- * Register a change event handler
453
- */
454
- change(handler: (e: Event) => void): this {
455
- return this.bind('change', handler);
456
- }
457
-
458
- /**
459
- * Register an input event handler
460
- */
461
- input(handler: (e: Event) => void): this {
462
- return this.bind('input', handler);
463
- }
464
-
465
- /**
466
- * Register a mouseover event handler
467
- */
468
- hover(handler: (e: MouseEvent) => void): this {
469
- return this.bind('mouseover', handler);
470
- }
471
-
472
- /**
473
- * Register a mouseout event handler
474
- */
475
- mouseout(handler: (e: MouseEvent) => void): this {
476
- return this.bind('mouseout', handler);
477
- }
478
-
479
- /**
480
- * Register a mousedown event handler
481
- */
482
- mousedown(handler: (e: MouseEvent) => void): this {
483
- return this.bind('mousedown', handler);
484
- }
485
-
486
- /**
487
- * Register a mouseup event handler
488
- */
489
- mouseup(handler: (e: MouseEvent) => void): this {
490
- return this.bind('mouseup', handler);
491
- }
492
-
493
- /**
494
- * Register a focus event handler (renamed to onFocus to avoid conflict with focus() method)
495
- */
496
- onFocus(handler: (e: FocusEvent) => void): this {
497
- return this.bind('focus', handler);
498
- }
499
-
500
- /**
501
- * Register a blur event handler (renamed to onBlur to avoid conflict with blur() method)
502
- */
503
- onBlur(handler: (e: FocusEvent) => void): this {
504
- return this.bind('blur', handler);
505
- }
506
-
507
- /**
508
- * Register a keydown event handler
509
- */
510
- keydown(handler: (e: KeyboardEvent) => void): this {
511
- return this.bind('keydown', handler);
512
- }
513
-
514
- /**
515
- * Register a keyup event handler
516
- */
517
- keyup(handler: (e: KeyboardEvent) => void): this {
518
- return this.bind('keyup', handler);
519
- }
520
-
521
- /**
522
- * Register a keypress event handler
523
- */
524
- keypress(handler: (e: KeyboardEvent) => void): this {
525
- return this.bind('keypress', handler);
526
- }
527
-
528
- /**
529
- * Register a submit event handler
530
- */
531
- submit(handler: (e: Event) => void): this {
532
- return this.bind('submit', handler);
533
- }
534
-
535
- /**
536
- * Register a scroll event handler
537
- */
538
- scroll(handler: (e: Event) => void): this {
539
- return this.bind('scroll', handler);
540
- }
541
-
542
- /**
543
- * Register a contextmenu (right-click) event handler
544
- */
545
- contextmenu(handler: (e: MouseEvent) => void): this {
546
- return this.bind('contextmenu', handler);
547
- }
548
-
549
- /* ═════════════════════════════════════════════════════════════════
550
- * TIME-TRAVEL DEBUGGING (PUBLIC API)
551
- * ═════════════════════════════════════════════════════════════════ */
552
-
553
- /**
554
- * Roll back to previous state
555
- */
556
- rollback(): this {
557
- const snapshot = stateHistory.moveBack();
558
- if (snapshot && snapshot.componentId === this._id) {
559
- stateHistory.startReplay();
560
- (this.state as any)[snapshot.property] = snapshot.oldValue;
561
- stateHistory.endReplay();
562
- }
563
- return this;
564
- }
565
-
566
- /**
567
- * Roll forward to next state
568
- */
569
- rollforward(): this {
570
- const snapshot = stateHistory.moveForward();
571
- if (snapshot && snapshot.componentId === this._id) {
572
- stateHistory.startReplay();
573
- (this.state as any)[snapshot.property] = snapshot.newValue;
574
- stateHistory.endReplay();
575
- }
576
- return this;
577
- }
578
-
579
- /**
580
- * Get complete timeline of this component's changes
581
- */
582
- timeline(): Array<any> {
583
- const stateChanges = stateHistory.getComponentHistory(this._id);
584
- const events = stateHistory.getComponentEvents(this._id);
585
- return [...stateChanges, ...events].sort((a, b) => a.timestamp - b.timestamp);
586
- }
587
-
588
- /**
589
- * Get only state changes for this component
590
- */
591
- stateHistory() {
592
- return stateHistory.getComponentHistory(this._id);
593
- }
594
-
595
- /**
596
- * Get only events for this component
597
- */
598
- eventHistory() {
599
- return stateHistory.getComponentEvents(this._id);
600
- }
601
-
602
- /* ═════════════════════════════════════════════════════════════════
603
- * COMMON RENDER HELPERS
604
- * ═════════════════════════════════════════════════════════════════ */
605
-
606
- protected _setupContainer(targetId?: string | HTMLElement | BaseComponent<any>): HTMLElement {
607
- // Determine Target Container Type
608
-
609
- let container: HTMLElement;
610
- if (targetId) {
611
- if (typeof targetId === 'string') {
612
- const id = targetId.startsWith('#') ? targetId.slice(1) : targetId;
613
- const target = document.getElementById(id);
614
- if (target) {
615
- container = target;
616
- } else {
617
- console.warn(`[Jux] Target "${targetId}" not found, creating it with graceful fallback`);
618
- container = getOrCreateContainer(id);
619
- }
620
- } else if (targetId instanceof HTMLElement) {
621
- container = targetId;
622
- } else if (targetId instanceof BaseComponent) {
623
- container = getOrCreateContainer(targetId._id);
624
- } else {
625
- throw new Error(`[Jux] Invalid targetId type: ${typeof targetId}`);
626
- }
627
- } else {
628
- container = getOrCreateContainer(this._id);
629
- }
630
-
631
- this.container = container;
632
-
633
- // ✅ Auto-register component when container is set up
634
- registry.register(this);
635
-
636
- return container;
637
- }
638
-
639
- protected _wireStandardEvents(element: HTMLElement): void {
640
- this._bindings.forEach(({ event, handler }) => {
641
- element.addEventListener(event, handler as EventListener);
642
- });
643
- }
644
-
645
- /**
646
- * Wire sync subscriptions with guard flag
647
- */
648
- protected _wireAllSyncs(): void {
649
- this._syncBindings.forEach(({ property, stateObj, toComponent }) => {
650
- const transform = toComponent || ((v: any) => v);
651
-
652
- const method = (this as any)[property];
653
-
654
- if (typeof method === 'function') {
655
- // Set initial value
656
- const initialValue = transform(stateObj.value);
657
- this._isUpdatingSync = true;
658
- method.call(this, initialValue);
659
- this._isUpdatingSync = false;
660
-
661
- // Subscribe to changes with guard
662
- stateObj.subscribe((val: any) => {
663
- if (this._isUpdatingSync) return;
664
-
665
- const transformed = transform(val);
666
- this._isUpdatingSync = true;
667
- method.call(this, transformed);
668
- this._isUpdatingSync = false;
669
- });
670
- } else {
671
- console.warn(
672
- `[Jux] ${this.constructor.name}.sync('${property}'): ` +
673
- `No method .${property}() found. Property will not be synced.`
674
- );
675
- }
676
- });
677
- }
678
-
679
- /* ═════════════════════════════════════════════════════════════════
680
- * PROPS ACCESSOR - Read-only access to component state
681
- * ═════════════════════════════════════════════════════════════════ */
682
-
683
- /**
684
- * ✅ Read-only accessor for component state (getter, not a method)
685
- *
686
- * Usage:
687
- * const myState = component.props; // ✅ Correct
688
- * const myState = component.props(); // ❌ Error: props is not a function
689
- */
690
- get props(): Readonly<TState> {
691
- return this.state as Readonly<TState>;
692
- }
693
-
694
- /* ═════════════════════════════════════════════════════════════════
695
- * FORM INPUT API (Optional - only used by form components)
696
- * ═════════════════════════════════════════════════════════════════ */
697
-
698
- /**
699
- * Set label for form inputs
700
- */
701
- label(value: string): this {
702
- (this.state as any).label = value;
703
- if (this._labelElement) {
704
- const requiredSpan = this._labelElement.querySelector('.jux-input-required');
705
- this._labelElement.textContent = value;
706
- if (requiredSpan) this._labelElement.appendChild(requiredSpan);
707
- }
708
- return this;
709
- }
710
-
711
- /**
712
- * Set required state for form inputs
713
- */
714
- required(value: boolean): this {
715
- (this.state as any).required = value;
716
- return this;
717
- }
718
-
719
- /**
720
- * Set name attribute for form inputs
721
- */
722
- name(value: string): this {
723
- (this.state as any).name = value;
724
- return this;
725
- }
726
-
727
- /**
728
- * Set custom validation handler
729
- */
730
- onValidate(handler: (value: any) => boolean | string): this {
731
- this._onValidate = handler;
732
- return this;
733
- }
734
-
735
- /**
736
- * Validate form input (override in form components)
737
- */
738
- validate(): boolean {
739
- console.warn(`${this.constructor.name}.validate() not implemented`);
740
- return true;
741
- }
742
-
743
- /**
744
- * Check if form input is valid (override in form components)
745
- */
746
- isValid(): boolean {
747
- console.warn(`${this.constructor.name}.isValid() not implemented`);
748
- return true;
749
- }
750
-
751
- /**
752
- * Get current value (override in form components)
753
- */
754
- getValue(): any {
755
- console.warn(`${this.constructor.name}.getValue() not implemented`);
756
- return undefined;
757
- }
758
-
759
- /**
760
- * Set current value (override in form components)
761
- */
762
- setValue(value: any): this {
763
- console.warn(`${this.constructor.name}.setValue() not implemented`);
764
- return this;
765
- }
766
-
767
- /* ═════════════════════════════════════════════════════════════════
768
- * FORM VALIDATION HELPERS (Protected - for form components)
769
- * ═════════════════════════════════════════════════════════════════ */
770
-
771
- protected _showError(message: string): void {
772
- if (this._errorElement) {
773
- this._errorElement.textContent = message;
774
- this._errorElement.style.display = 'block';
775
- }
776
-
777
- if (this._inputElement) {
778
- this._inputElement.classList.add('jux-input-invalid');
779
- }
780
-
781
- (this.state as any).errorMessage = message;
782
- }
783
-
784
- protected _clearError(): void {
785
- if (this._errorElement) {
786
- this._errorElement.textContent = '';
787
- this._errorElement.style.display = 'none';
788
- }
789
-
790
- if (this._inputElement) {
791
- this._inputElement.classList.remove('jux-input-invalid');
792
- }
793
-
794
- (this.state as any).errorMessage = undefined;
795
- }
796
-
797
- /**
798
- * Build label element for form inputs
799
- */
800
- protected _renderLabel(): HTMLLabelElement {
801
- const label = (this.state as any).label || formatIdAsLabel(this._id); // ✅ Auto-generate
802
- const required = (this.state as any).required || false;
803
-
804
- const labelEl = document.createElement('label');
805
- labelEl.className = 'jux-input-label';
806
- labelEl.htmlFor = `${this._id}-input`;
807
- labelEl.textContent = label;
808
-
809
- if (required) {
810
- const requiredSpan = document.createElement('span');
811
- requiredSpan.className = 'jux-input-required';
812
- requiredSpan.textContent = ' *';
813
- labelEl.appendChild(requiredSpan);
814
- }
815
-
816
- this._labelElement = labelEl;
817
- return labelEl;
818
- }
819
-
820
- /**
821
- * Build error element for form inputs
822
- */
823
- protected _renderError(): HTMLElement {
824
- const errorEl = document.createElement('div');
825
- errorEl.className = 'jux-input-error';
826
- errorEl.id = `${this._id}-error`;
827
- errorEl.style.display = 'none';
828
-
829
- this._errorElement = errorEl;
830
- return errorEl;
831
- }
832
-
833
- /**
834
- * Wire up two-way sync for form inputs
835
- */
836
- protected _wireFormSync(inputElement: HTMLElement, eventName: string = 'input'): void {
837
- const valueSync = this._syncBindings.find(b => b.property === 'value');
838
-
839
- if (valueSync) {
840
- const { stateObj, toState, toComponent } = valueSync;
841
-
842
- const transformToState = toState || ((v: any) => v);
843
- const transformToComponent = toComponent || ((v: any) => v);
844
-
845
- let isUpdating = false;
846
-
847
- // State → Component
848
- stateObj.subscribe((val: any) => {
849
- if (isUpdating) return;
850
- const transformed = transformToComponent(val);
851
- this.setValue(transformed);
852
- });
853
-
854
- // Component → State
855
- inputElement.addEventListener(eventName, () => {
856
- if (isUpdating) return;
857
- isUpdating = true;
858
-
859
- const value = this.getValue();
860
- const transformed = transformToState(value);
861
- this._clearError();
862
-
863
- stateObj.set(transformed);
864
-
865
- setTimeout(() => { isUpdating = false; }, 0);
866
- });
867
- } else {
868
- // Default behavior without sync
869
- inputElement.addEventListener(eventName, () => {
870
- this._clearError();
871
- });
872
- }
873
-
874
- // Only validate on blur IF the field has been validated before
875
- inputElement.addEventListener('blur', () => {
876
- if (this._hasBeenValidated) {
877
- this.validate();
878
- }
879
- });
880
- }
881
- }
882
-
883
- // ✅ Type assertion for Animations (all components have animation methods)
884
- export interface BaseComponent extends Animations { }