juxscript 1.1.239 → 1.1.243

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/tag.ts +68 -0
  4. package/lib/styles/shadcn.css +20 -7
  5. package/lib/utils/idgen.ts +6 -0
  6. package/package.json +5 -6
  7. package/index.d.ts +0 -239
  8. package/index.d.ts.map +0 -1
  9. package/lib/components/alert.d.ts +0 -36
  10. package/lib/components/alert.d.ts.map +0 -1
  11. package/lib/components/alert.js +0 -172
  12. package/lib/components/alert.ts +0 -219
  13. package/lib/components/app.d.ts +0 -89
  14. package/lib/components/app.d.ts.map +0 -1
  15. package/lib/components/app.js +0 -175
  16. package/lib/components/app.ts +0 -247
  17. package/lib/components/badge.d.ts +0 -26
  18. package/lib/components/badge.d.ts.map +0 -1
  19. package/lib/components/badge.js +0 -91
  20. package/lib/components/badge.ts +0 -118
  21. package/lib/components/base/Animations.d.ts +0 -36
  22. package/lib/components/base/Animations.d.ts.map +0 -1
  23. package/lib/components/base/Animations.js +0 -70
  24. package/lib/components/base/Animations.ts +0 -112
  25. package/lib/components/base/BaseComponent.d.ts +0 -294
  26. package/lib/components/base/BaseComponent.d.ts.map +0 -1
  27. package/lib/components/base/BaseComponent.js +0 -735
  28. package/lib/components/base/BaseComponent.ts +0 -884
  29. package/lib/components/base/FormInput.d.ts +0 -77
  30. package/lib/components/base/FormInput.d.ts.map +0 -1
  31. package/lib/components/base/FormInput.js +0 -171
  32. package/lib/components/base/FormInput.ts +0 -237
  33. package/lib/components/blueprint.d.ts +0 -40
  34. package/lib/components/blueprint.d.ts.map +0 -1
  35. package/lib/components/blueprint.js +0 -327
  36. package/lib/components/button.d.ts +0 -70
  37. package/lib/components/button.d.ts.map +0 -1
  38. package/lib/components/button.js +0 -177
  39. package/lib/components/button.ts +0 -237
  40. package/lib/components/card.d.ts +0 -35
  41. package/lib/components/card.d.ts.map +0 -1
  42. package/lib/components/card.js +0 -130
  43. package/lib/components/card.ts +0 -177
  44. package/lib/components/chart.d.ts +0 -49
  45. package/lib/components/chart.d.ts.map +0 -1
  46. package/lib/components/chart.js +0 -205
  47. package/lib/components/chart.ts +0 -254
  48. package/lib/components/checkbox.d.ts +0 -33
  49. package/lib/components/checkbox.d.ts.map +0 -1
  50. package/lib/components/checkbox.js +0 -202
  51. package/lib/components/checkbox.ts +0 -260
  52. package/lib/components/code.d.ts +0 -52
  53. package/lib/components/code.d.ts.map +0 -1
  54. package/lib/components/code.js +0 -201
  55. package/lib/components/code.ts +0 -260
  56. package/lib/components/container.d.ts +0 -60
  57. package/lib/components/container.d.ts.map +0 -1
  58. package/lib/components/container.js +0 -195
  59. package/lib/components/container.ts +0 -259
  60. package/lib/components/data.d.ts +0 -36
  61. package/lib/components/data.d.ts.map +0 -1
  62. package/lib/components/data.js +0 -110
  63. package/lib/components/data.ts +0 -135
  64. package/lib/components/dataframe/DataFrameSource.d.ts +0 -118
  65. package/lib/components/dataframe/DataFrameSource.d.ts.map +0 -1
  66. package/lib/components/dataframe/DataFrameSource.js +0 -421
  67. package/lib/components/dataframe/DataFrameSource.ts +0 -532
  68. package/lib/components/dataframe/ImportSettingsModal.d.ts +0 -60
  69. package/lib/components/dataframe/ImportSettingsModal.d.ts.map +0 -1
  70. package/lib/components/dataframe/ImportSettingsModal.js +0 -442
  71. package/lib/components/dataframe/ImportSettingsModal.ts +0 -531
  72. package/lib/components/dataframe.d.ts +0 -110
  73. package/lib/components/dataframe.d.ts.map +0 -1
  74. package/lib/components/dataframe.js +0 -470
  75. package/lib/components/datepicker.d.ts +0 -40
  76. package/lib/components/datepicker.d.ts.map +0 -1
  77. package/lib/components/datepicker.js +0 -193
  78. package/lib/components/datepicker.ts +0 -251
  79. package/lib/components/dialog.d.ts +0 -39
  80. package/lib/components/dialog.d.ts.map +0 -1
  81. package/lib/components/dialog.js +0 -131
  82. package/lib/components/dialog.ts +0 -178
  83. package/lib/components/divider.d.ts +0 -31
  84. package/lib/components/divider.d.ts.map +0 -1
  85. package/lib/components/divider.js +0 -72
  86. package/lib/components/divider.ts +0 -104
  87. package/lib/components/dropdown-menu.d.ts +0 -42
  88. package/lib/components/dropdown-menu.d.ts.map +0 -1
  89. package/lib/components/dropdown-menu.js +0 -177
  90. package/lib/components/dropdown-menu.ts +0 -214
  91. package/lib/components/dropdown.d.ts +0 -41
  92. package/lib/components/dropdown.d.ts.map +0 -1
  93. package/lib/components/dropdown.js +0 -136
  94. package/lib/components/dropdown.ts +0 -188
  95. package/lib/components/element.d.ts +0 -51
  96. package/lib/components/element.d.ts.map +0 -1
  97. package/lib/components/element.js +0 -209
  98. package/lib/components/element.ts +0 -271
  99. package/lib/components/event-chain.d.ts +0 -9
  100. package/lib/components/event-chain.d.ts.map +0 -1
  101. package/lib/components/event-chain.js +0 -33
  102. package/lib/components/fileupload.d.ts +0 -98
  103. package/lib/components/fileupload.d.ts.map +0 -1
  104. package/lib/components/fileupload.js +0 -351
  105. package/lib/components/fileupload.ts +0 -449
  106. package/lib/components/grid.d.ts +0 -88
  107. package/lib/components/grid.d.ts.map +0 -1
  108. package/lib/components/grid.js +0 -208
  109. package/lib/components/grid.ts +0 -295
  110. package/lib/components/heading.d.ts +0 -25
  111. package/lib/components/heading.d.ts.map +0 -1
  112. package/lib/components/heading.js +0 -83
  113. package/lib/components/heading.ts +0 -113
  114. package/lib/components/helpers.d.ts +0 -9
  115. package/lib/components/helpers.d.ts.map +0 -1
  116. package/lib/components/helpers.js +0 -30
  117. package/lib/components/helpers.ts +0 -41
  118. package/lib/components/hero.d.ts +0 -60
  119. package/lib/components/hero.d.ts.map +0 -1
  120. package/lib/components/hero.js +0 -239
  121. package/lib/components/hero.ts +0 -302
  122. package/lib/components/history/StateHistory.d.ts +0 -91
  123. package/lib/components/history/StateHistory.d.ts.map +0 -1
  124. package/lib/components/history/StateHistory.js +0 -154
  125. package/lib/components/history/StateHistory.ts +0 -200
  126. package/lib/components/icon.d.ts +0 -36
  127. package/lib/components/icon.d.ts.map +0 -1
  128. package/lib/components/icon.js +0 -135
  129. package/lib/components/icon.ts +0 -182
  130. package/lib/components/icons.d.ts +0 -25
  131. package/lib/components/icons.d.ts.map +0 -1
  132. package/lib/components/icons.js +0 -440
  133. package/lib/components/icons.ts +0 -464
  134. package/lib/components/image.d.ts +0 -42
  135. package/lib/components/image.d.ts.map +0 -1
  136. package/lib/components/image.js +0 -204
  137. package/lib/components/image.ts +0 -260
  138. package/lib/components/include.d.ts +0 -86
  139. package/lib/components/include.d.ts.map +0 -1
  140. package/lib/components/include.js +0 -238
  141. package/lib/components/include.ts +0 -281
  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 { }