lilact 0.0.0

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 (164) hide show
  1. package/Icon/r +0 -0
  2. package/LICENSE.TXT +26 -0
  3. package/README.md +116 -0
  4. package/bin/copy-demo.js +26 -0
  5. package/bin/transpile-dir.js +112 -0
  6. package/bin/transpile.js +137 -0
  7. package/dist/lilact.development.js +9344 -0
  8. package/dist/lilact.development.js.map +1 -0
  9. package/dist/lilact.production.min.js +63 -0
  10. package/dist/lilact.production.min.js.map +1 -0
  11. package/docs/.nojekyll +1 -0
  12. package/docs/assets/hierarchy.js +1 -0
  13. package/docs/assets/highlight.css +71 -0
  14. package/docs/assets/icons.js +18 -0
  15. package/docs/assets/icons.svg +1 -0
  16. package/docs/assets/main.js +60 -0
  17. package/docs/assets/navigation.js +1 -0
  18. package/docs/assets/search.js +1 -0
  19. package/docs/assets/style.css +1633 -0
  20. package/docs/classes/accessories.ErrorBoundary.html +37 -0
  21. package/docs/classes/accessories.Suspense.html +41 -0
  22. package/docs/classes/components.Component.html +45 -0
  23. package/docs/classes/components.HTMLComponent.html +32 -0
  24. package/docs/classes/components.RootComponent.html +32 -0
  25. package/docs/classes/transition.Transition.html +31 -0
  26. package/docs/functions/accessories.Spinner.html +9 -0
  27. package/docs/functions/components.createComponent.html +8 -0
  28. package/docs/functions/components.createRoot.html +5 -0
  29. package/docs/functions/components.render.html +5 -0
  30. package/docs/functions/hooks.createContext.html +3 -0
  31. package/docs/functions/hooks.useActionState.html +3 -0
  32. package/docs/functions/hooks.useCallback.html +5 -0
  33. package/docs/functions/hooks.useContext.html +3 -0
  34. package/docs/functions/hooks.useDeferredValue.html +4 -0
  35. package/docs/functions/hooks.useEffect.html +3 -0
  36. package/docs/functions/hooks.useHook.html +5 -0
  37. package/docs/functions/hooks.useId.html +4 -0
  38. package/docs/functions/hooks.useLayoutEffect.html +3 -0
  39. package/docs/functions/hooks.useLocalStorage.html +4 -0
  40. package/docs/functions/hooks.useMemo.html +4 -0
  41. package/docs/functions/hooks.useReducer.html +6 -0
  42. package/docs/functions/hooks.useRef.html +4 -0
  43. package/docs/functions/hooks.useState.html +3 -0
  44. package/docs/functions/hooks.useTransition.html +3 -0
  45. package/docs/functions/jsx.transpileJSX.html +6 -0
  46. package/docs/functions/misc.Fragment.html +3 -0
  47. package/docs/functions/misc.classNames.html +2 -0
  48. package/docs/functions/misc.deepEqual.html +3 -0
  49. package/docs/functions/misc.findDOMNode.html +3 -0
  50. package/docs/functions/misc.getComponentByPointer.html +3 -0
  51. package/docs/functions/misc.isAsync.html +3 -0
  52. package/docs/functions/misc.isClass.html +3 -0
  53. package/docs/functions/misc.isEmpty.html +3 -0
  54. package/docs/functions/misc.isError.html +3 -0
  55. package/docs/functions/misc.isThenable.html +3 -0
  56. package/docs/functions/misc.isValidElement.html +3 -0
  57. package/docs/functions/misc.shallowEqual.html +3 -0
  58. package/docs/functions/redux.Provider.html +5 -0
  59. package/docs/functions/redux.connect.html +5 -0
  60. package/docs/functions/redux.useDispatch.html +3 -0
  61. package/docs/functions/redux.useSelector.html +5 -0
  62. package/docs/functions/redux.useStore.html +3 -0
  63. package/docs/functions/router.HashRouter.html +5 -0
  64. package/docs/functions/router.Link.html +12 -0
  65. package/docs/functions/router.NavLink.html +10 -0
  66. package/docs/functions/router.Route.html +6 -0
  67. package/docs/functions/router.Routes.html +4 -0
  68. package/docs/functions/router.useLocation.html +3 -0
  69. package/docs/functions/router.useNavigate.html +3 -0
  70. package/docs/functions/run.require.html +14 -0
  71. package/docs/functions/run.run.html +13 -0
  72. package/docs/functions/run.runScripts.html +7 -0
  73. package/docs/functions/run.traceError.html +6 -0
  74. package/docs/functions/timers.animationFramePromise.html +3 -0
  75. package/docs/functions/timers.clearInterval.html +2 -0
  76. package/docs/functions/timers.clearTimeout.html +2 -0
  77. package/docs/functions/timers.grabTimers.html +3 -0
  78. package/docs/functions/timers.pauseTimers.html +2 -0
  79. package/docs/functions/timers.releaseTimers.html +2 -0
  80. package/docs/functions/timers.resetTimers.html +2 -0
  81. package/docs/functions/timers.resumeTimers.html +2 -0
  82. package/docs/functions/timers.setInterval.html +5 -0
  83. package/docs/functions/timers.setTimeout.html +4 -0
  84. package/docs/functions/timers.timeoutPromise.html +4 -0
  85. package/docs/functions/transition.CSSTransition.html +18 -0
  86. package/docs/functions/transition.TransitionGroup.html +5 -0
  87. package/docs/hierarchy.html +1 -0
  88. package/docs/index.html +88 -0
  89. package/docs/media/icon.png +0 -0
  90. package/docs/modules/accessories.html +1 -0
  91. package/docs/modules/components.html +1 -0
  92. package/docs/modules/hooks.html +1 -0
  93. package/docs/modules/jsx.html +1 -0
  94. package/docs/modules/misc.html +1 -0
  95. package/docs/modules/redux.html +1 -0
  96. package/docs/modules/router.html +1 -0
  97. package/docs/modules/run.html +1 -0
  98. package/docs/modules/timers.html +1 -0
  99. package/docs/modules/transition.html +1 -0
  100. package/docs/modules.html +1 -0
  101. package/docs/static/demos/actionstate.jsx +41 -0
  102. package/docs/static/demos/boundary.jsx +52 -0
  103. package/docs/static/demos/context.jsx +68 -0
  104. package/docs/static/demos/css-transition.jsx +65 -0
  105. package/docs/static/demos/modal.jsx +41 -0
  106. package/docs/static/demos/proptypes.jsx +37 -0
  107. package/docs/static/demos/reducer.jsx +71 -0
  108. package/docs/static/demos/redux.jsx +58 -0
  109. package/docs/static/demos/router.jsx +153 -0
  110. package/docs/static/demos/stopwatch.jsx +42 -0
  111. package/docs/static/demos/suspense.jsx +48 -0
  112. package/docs/static/demos/transition.jsx +40 -0
  113. package/docs/static/demos/use-differed.jsx +30 -0
  114. package/docs/static/demos/usetransition.jsx +37 -0
  115. package/docs/static/index.html +212 -0
  116. package/docs/static/lilact.development.js +9344 -0
  117. package/docs/static/lilact.production.min.js +63 -0
  118. package/docs/static/prismjs/LICENSE.md +21 -0
  119. package/docs/static/prismjs/prism-jsx.min.js +1 -0
  120. package/docs/static/prismjs/prism.min.css +1 -0
  121. package/docs/static/prismjs/prism.min.js +16 -0
  122. package/docs/variables/jsx.transpilerConfig.html +1 -0
  123. package/docs/variables/misc.Children.html +10 -0
  124. package/icon.png +0 -0
  125. package/package.json +39 -0
  126. package/root/demos/actionstate.jsx +41 -0
  127. package/root/demos/boundary.jsx +52 -0
  128. package/root/demos/context.jsx +68 -0
  129. package/root/demos/css-transition.jsx +65 -0
  130. package/root/demos/modal.jsx +41 -0
  131. package/root/demos/proptypes.jsx +37 -0
  132. package/root/demos/reducer.jsx +71 -0
  133. package/root/demos/redux.jsx +58 -0
  134. package/root/demos/router.jsx +153 -0
  135. package/root/demos/stopwatch.jsx +42 -0
  136. package/root/demos/suspense.jsx +48 -0
  137. package/root/demos/transition.jsx +40 -0
  138. package/root/demos/use-differed.jsx +30 -0
  139. package/root/demos/usetransition.jsx +37 -0
  140. package/root/index.html +212 -0
  141. package/root/lilact.development.js +9344 -0
  142. package/root/lilact.production.min.js +63 -0
  143. package/root/prismjs/LICENSE.md +21 -0
  144. package/root/prismjs/prism-jsx.min.js +1 -0
  145. package/root/prismjs/prism.min.css +1 -0
  146. package/root/prismjs/prism.min.js +16 -0
  147. package/src/accessories.jsx +287 -0
  148. package/src/components.jsx +1063 -0
  149. package/src/events.jsx +175 -0
  150. package/src/hooks.jsx +461 -0
  151. package/src/jsx.addons.js +70 -0
  152. package/src/jsx.js +1168 -0
  153. package/src/lilact.jsx +115 -0
  154. package/src/loader.cjs +59 -0
  155. package/src/misc.jsx +419 -0
  156. package/src/redux.jsx +198 -0
  157. package/src/router.jsx +282 -0
  158. package/src/run.jsx +275 -0
  159. package/src/timers.jsx +313 -0
  160. package/src/transition.jsx +244 -0
  161. package/src/vlq.js +94 -0
  162. package/tsconfig.json +15 -0
  163. package/typedoc.json +23 -0
  164. package/webpack.config.js +196 -0
@@ -0,0 +1,1063 @@
1
+ /*
2
+
3
+ Lilact
4
+ Copyright (C) 2024-2026 Arash Kazemi <contact.arash.kazemi@gmail.com>
5
+ All rights reserved.
6
+
7
+ BSD-2-Clause
8
+
9
+ Redistribution and use in source and binary forms, with or without
10
+ modification, are permitted provided that the following conditions are met:
11
+
12
+ * Redistributions of source code must retain the above copyright
13
+ notice, this list of conditions and the following disclaimer.
14
+ * Redistributions in binary form must reproduce the above copyright
15
+ notice, this list of conditions and the following disclaimer in the
16
+ documentation and/or other materials provided with the distribution.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
22
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ */
30
+
31
+ ʔ defineSymbols ( "LILACT", [ "CORE", "COMPONENT", "TEXT", "IS_ZOMBIE", "IDX", "CHILD_CLASS_ADDENDUM", "MEMOIZED"] ) ʔ
32
+
33
+ import {shallowEqual} from "./misc.jsx";
34
+ /*
35
+ ComponentCache is for internal use. It is the heart of the JSX runtime,
36
+ it holds child components and detects which one is being rendered or updated.
37
+ */
38
+
39
+ class ComponentCache
40
+ {
41
+ owner;
42
+ current_map = new Map;
43
+ new_map = new Map;
44
+ pick_index = 0;
45
+
46
+ constructor(owner)
47
+ {
48
+ this.owner = owner;
49
+ }
50
+
51
+ pick(key, construct_func)
52
+ {
53
+ let comp;
54
+ let buck = this.current_map.get(key);
55
+
56
+ if(buck && buck.length>buck[IDX]) {
57
+ comp = buck[buck[IDX]];
58
+ buck[IDX]++;
59
+
60
+ buck = this.new_map.get(key);
61
+ if(buck!==undefined) {
62
+ buck.push( comp );
63
+ }
64
+ else {
65
+ buck = [ comp ];
66
+ this.new_map.set(key, buck);
67
+ buck[IDX]=0;
68
+ }
69
+ }
70
+ else {
71
+ comp = construct_func();
72
+
73
+ buck = this.new_map.get(key);
74
+ if(buck!==undefined) {
75
+ buck.push( comp );
76
+ }
77
+ else {
78
+ buck = [ comp ];
79
+ this.new_map.set(key, buck);
80
+ buck[IDX]=0;
81
+ }
82
+
83
+ if(comp[CORE]) comp[CORE].parent ??= this.owner;
84
+ }
85
+
86
+ return comp;
87
+ }
88
+
89
+ commit()
90
+ {
91
+ this.current_map.forEach( (arr)=>{
92
+ arr.slice(arr[IDX]).forEach((ex)=>{
93
+ if(ex.cleanup) ex.cleanup();
94
+ });
95
+ });
96
+
97
+ this.current_map = this.new_map;
98
+ this.new_map = new Map;
99
+ }
100
+
101
+ }
102
+
103
+ /** ComponentCore - Mostly for internal use. This is where all the component data and methods
104
+ * used by lilact are kept. The Component class uses it under the hood, so there is a separation
105
+ * and user can set whatever property they want in the component. Each Lilact.Component
106
+ * has a core that is accessible via lilact symbol CORE, i.e. component[CORE]. Note
107
+ * that you should either define a LILACT:CORE symbol or use the lilact preprocessor
108
+ * tools which is a wiser choice.
109
+ *
110
+ * ComponentCore methods are not to be called by the user. But it can also be used
111
+ * to store data more efficiently, and I have used it extensively. But it is
112
+ * better for the user to work according react paradigms instead if memory
113
+ * efficiency is not a high priority. It is not compatible with React API. So if
114
+ * you want to do so, check the code to prevent accidental shadowing of the methods
115
+ * and properties.
116
+ *
117
+ * @class ComponentCore
118
+ */
119
+
120
+ class ComponentCore
121
+ {
122
+ /*
123
+
124
+ // these are commented so they are not allocated by default. i just wanted to keep a list.
125
+
126
+ entity = undefined
127
+
128
+ ref = undefined;
129
+
130
+ context = undefined;
131
+ state = undefined;
132
+
133
+ container = null;
134
+ outlet = null;
135
+
136
+ depo = new ComponentCache;
137
+
138
+ element = undefined;
139
+ mount_state
140
+
141
+ insert_index
142
+ loader_args
143
+
144
+ */
145
+
146
+ component;
147
+ props;
148
+
149
+ constructor(comp, props)
150
+ {
151
+ this.component = comp;
152
+ this.props = props || {};
153
+ }
154
+
155
+ apply(next_props = this.props, next_state = this.next_state || this.state)
156
+ {
157
+ //let do_rerender = true;
158
+
159
+ // if(this.outlet && this.entity[MEMOIZED]) {
160
+ // if(shallowEqual(this.props, next_props)) do_rerender=false;
161
+ // delete this.entity[MEMOIZED];
162
+ // }
163
+
164
+ /*if(do_rerender)*/ {
165
+ ʔ if(DEBUG) {
166
+
167
+ if(this.entity?.propTypes) {
168
+ Lilact.PropTypes.checkPropTypes(this.entity.propTypes, this.props, 'prop', this.entity.name);
169
+ }
170
+ else if(this.component?.propTypes) {
171
+ Lilact.PropTypes.checkPropTypes(this.component.propTypes, this.props, 'prop', this.component.name);
172
+ }
173
+
174
+ ʔ }
175
+ if(typeof(next_state)==='function') next_state = next_state(this.state);
176
+
177
+ if(this.component.constructor.defaultProps) {
178
+ next_props = {...this.component.constructor.defaultProps, ...next_props};
179
+ }
180
+
181
+ if( this.component.shouldComponentUpdate &&
182
+ !this.component.shouldComponentUpdate
183
+ (next_state, next_props, this.context) ) return;
184
+
185
+
186
+ if( typeof(this.entity)==='string' ) {
187
+ if(!(this.element instanceof Element)) {
188
+ this.element = document.createElement(this.entity);
189
+ }
190
+ this.element[COMPONENT] = this.component;
191
+ }
192
+
193
+
194
+ if(next_props.ref) {
195
+ if(this.element) {
196
+ next_props.ref.current = this.element;
197
+ }
198
+ else {
199
+ next_props.ref.current = this.component;
200
+ }
201
+ }
202
+
203
+ if(next_props!==undefined && this.component.componentWillReceiveProps) {
204
+ this.component.componentWillReceiveProps(next_props);
205
+ }
206
+
207
+ if(this.component.componentWillUpdate) {
208
+ this.component.componentWillUpdate(next_props, next_state);
209
+ }
210
+
211
+ const prev_state = this.state, prev_props=this.props;
212
+
213
+ if(this.element) {
214
+ this.updateElementProps(next_props);
215
+ }
216
+ this.props = next_props;
217
+
218
+ if(this.state!==undefined || next_state!==undefined) this.state = next_state;
219
+ else delete this.state;
220
+
221
+ if(this.next_state) delete this.next_state;
222
+
223
+
224
+ if(this.hooks!==undefined) {
225
+ this.hook_index = 0;
226
+ Lilact.current_component = [this, Lilact.current_component];
227
+
228
+ try {
229
+ this.outlet = this.component.render(next_props);
230
+ }
231
+ catch(e) {
232
+ renderErrorHandler(this, e);
233
+ }
234
+
235
+ Lilact.current_component = Lilact.current_component[1];
236
+ }
237
+ else {
238
+ try {
239
+ this.outlet = this.component.render();
240
+ }
241
+ catch(e) {
242
+ renderErrorHandler(this, e);
243
+ }
244
+ }
245
+
246
+ if(this.outlet?.constructor?.name!=='Array') {
247
+ this.outlet = [this.outlet];
248
+ }
249
+
250
+ this.outlet = [...this.outlet];
251
+
252
+ for (let i=0;i<this.outlet.length;i++) {
253
+ let item = this.outlet[i];
254
+
255
+ if(item===undefined || item===null || typeof(item)==='boolean') {
256
+ this.outlet.splice(i, 1);
257
+ i--;
258
+ }
259
+ else if(typeof item==='function') {
260
+ const res = this.childFunctionHandler(item);
261
+ this.outlet.splice(i, 1, res);
262
+ i--;
263
+ }
264
+ else if(item.constructor.name === 'Array') {
265
+ this.outlet.splice(i, 1, ...item);
266
+ i--;
267
+ }
268
+ else {
269
+ const core = prepareCore(this, item);
270
+ this.outlet[i] = core;
271
+
272
+ if(core[TEXT]===undefined) {
273
+ core.container= this.element? this : this.container;
274
+ core.apply(item.props);
275
+ }
276
+ else {
277
+ if(!core.element) {
278
+ core.element = document.createTextNode( item[TEXT]);
279
+ core[TEXT] = item[TEXT];
280
+ }
281
+ else if(core[TEXT]!==item[TEXT]) {
282
+ core.element.textContent = item[TEXT];
283
+ core[TEXT] = item[TEXT];
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ if(this.cache) this.cache.commit();
290
+
291
+ }
292
+ if(this.element) this.arrangeOutlet();
293
+
294
+ // TODO: should componentDidUpdate be called after arranging/appending the outlet or before?
295
+ if(this.component.componentDidUpdate) {
296
+ this.component.componentDidUpdate(prev_props, prev_state, this.last_snapshot);
297
+ }
298
+
299
+ if(this.last_snapshot) delete this.last_snapshot;
300
+
301
+ }
302
+
303
+ async cleanup()
304
+ {
305
+ try {
306
+ const promises = [];
307
+
308
+ if(this.component.componentWillUnmount) {
309
+ this.component.componentWillUnmount();
310
+ }
311
+
312
+ if(this?.element?.parentElement) {
313
+ this.element.parentElement.removeChild( this.element );
314
+ }
315
+
316
+ if(this.outlet!==undefined) {
317
+ for(let c of this.outlet) {
318
+ if(c.cleanup) {
319
+ c.cleanup();
320
+ }
321
+ }
322
+ }
323
+
324
+ if(this.props?.children!==undefined) {
325
+ for(let c of this.props.children) {
326
+ if(c.cleanup) {
327
+ c.cleanup();
328
+ }
329
+ }
330
+ }
331
+
332
+ if(this.hooks!==undefined) {
333
+ for(let h of this.hooks) {
334
+ if(h.cleanup) {
335
+ h.cleanup();
336
+ }
337
+ }
338
+ }
339
+ }
340
+
341
+ catch(e) {
342
+ // todo: should did catch be called? and state be modified?
343
+ /*if(this.component.componentDidCatch) {
344
+ this.component.componentDidCatch(e);
345
+ }
346
+ else */
347
+ throw(e);
348
+ }
349
+ }
350
+
351
+ updateElementProps(patch, force=false)
352
+ {
353
+ if(this.entity==="input") {
354
+ if(patch?.type!==this.element.type) {
355
+ this.element.type=patch.type;
356
+ }
357
+
358
+ if(patch?.value!==this.element.value) {
359
+ this.element.value=patch.value;
360
+ }
361
+
362
+ if(patch?.type==="checkbox") {
363
+ const chk = !!patch?.checked;
364
+
365
+ if(this.element.checked != chk || this.element.parentNode===null) {
366
+ this.element.checked = chk;
367
+ }
368
+ }
369
+ }
370
+
371
+ // old ones that don't exist in the new one
372
+ for(let a in this.props) {
373
+ const al = a.toLowerCase();
374
+
375
+ if( !patch.hasOwnProperty(a) ) {
376
+
377
+ if( Lilact.events_set.has(al) ) {
378
+ this.event_detachers[al]();
379
+ }
380
+ else {
381
+ this.element.setAttribute(a, undefined);
382
+ }
383
+ }
384
+ }
385
+
386
+ for(let a in patch) {
387
+ const al = a.toLowerCase();
388
+
389
+ if( Lilact.special_attributes.has(al) ) continue;
390
+
391
+ if( patch===this.props || !Lilact.defaultIsEqual(patch[a], this.props[a]) || force ) {
392
+
393
+ if( Lilact.events_set.has(al) ) {
394
+ this.event_detachers ??= {};
395
+ this.event_detachers[al]?.();
396
+ this.event_detachers[al] = Lilact.addWrappedEventListener(this.element, al.substring(2), patch[a]);
397
+ }
398
+ else if(al==='style') {
399
+ Object.assign(this.element.style, patch[a]);
400
+ }
401
+ else {
402
+ this.element.setAttribute(al, patch[a]);
403
+ }
404
+ }
405
+ }
406
+
407
+
408
+ if(patch?.action) {
409
+ this.element.onsubmit = patch.action;
410
+ }
411
+ else {
412
+ this.element.onsubmit = undefined;
413
+ }
414
+
415
+ ʔ if(DEBUG) {
416
+ this.element.setAttribute('key', this.props.key);
417
+ ʔ }
418
+
419
+ this.updateElementClass(patch);
420
+ }
421
+
422
+ updateElementClass(patch=this.props)
423
+ {
424
+ let cn = patch?.className;
425
+ cn ??= patch?.class ? patch.class : '';
426
+
427
+ if(this?.parent?.[CHILD_CLASS_ADDENDUM]) {
428
+ cn += ' ' + this?.parent?.[CHILD_CLASS_ADDENDUM];
429
+ }
430
+
431
+ if(cn.length>0) {
432
+ cn = cn.split(/\s+/g);
433
+ for(const n of Array.from(this.element.classList)) {
434
+ if(cn.indexOf(n)===-1) {
435
+ this.element.classList.remove(n);
436
+ }
437
+ }
438
+ for(const n of cn) {
439
+ if(n.length>0) {
440
+ this.element.classList.add(n);
441
+ }
442
+ }
443
+ }
444
+ else {
445
+ delete this.element.className;
446
+ }
447
+ }
448
+
449
+
450
+ scanZombies(container, next_element)
451
+ {
452
+ const chs = container.element.childNodes;
453
+ while( chs[container.insert_index] &&
454
+ chs[container.insert_index][IS_ZOMBIE] &&
455
+ chs[container.insert_index]!==next_element )
456
+ {
457
+ container.insert_index++;
458
+ }
459
+ }
460
+
461
+ appendElement(core)
462
+ {
463
+ this.scanZombies(core.container, core.element);
464
+
465
+ if(core?.element.parentNode===null) {
466
+ core.container.element.insertBefore(
467
+ core.element,
468
+ core.container.element.childNodes[core.container.insert_index] || null
469
+ );
470
+
471
+ if(core?.component?.componentDidMount) {
472
+ core.component.componentDidMount();
473
+ }
474
+
475
+ }
476
+ else {
477
+ if(core.container.element.childNodes[core.container.insert_index]!==core.element) {
478
+ core.container.element.insertBefore(
479
+ core.element,
480
+ core.container.element.childNodes[core.container.insert_index] || null
481
+ );
482
+ }
483
+ }
484
+ core.container.insert_index++;
485
+ }
486
+
487
+ arrangeOutlet()
488
+ {
489
+ this.insert_index = 0;
490
+
491
+ for(const core of this.outlet) {
492
+ if(core) {
493
+ if(core.element) {
494
+ core.container = this.element?this:this.container;
495
+ core.container.appendElement(core);
496
+ }
497
+ else {
498
+ if(core.arrangeOutlet) core.arrangeOutlet();
499
+
500
+ // todo: is there a way to remove this useless flag?
501
+ if(!core?.mounted) {
502
+ core.mounted = true;
503
+ if(core?.component?.componentDidMount) {
504
+ core.component.componentDidMount();
505
+ }
506
+
507
+ }
508
+
509
+ }
510
+ }
511
+ }
512
+
513
+ }
514
+
515
+ // note: override this to tailor function children like <Transition>{(state)=>{...}}</Transition>
516
+ childFunctionHandler(func) {
517
+ return func(this.state);
518
+ }
519
+
520
+ }
521
+
522
+
523
+
524
+ const renderErrorHandler = (c, e) =>
525
+ {
526
+ const stack = [c];
527
+
528
+ while(c && !c?.component?.componentDidCatch) {
529
+ c = c.parent;
530
+ if(c) stack.push(c);
531
+ }
532
+ if(c?.component?.componentDidCatch) {
533
+ if(c.entity?.getDerivedStateFromError) {
534
+ c.component.setState({...c.component.state, ...c.entity.getDerivedStateFromError.call(c, e)});
535
+ }
536
+
537
+ if(Lilact.isError(e)) {
538
+ e = Lilact.traceError(e);
539
+ }
540
+
541
+ let stack_log = Array.prototype
542
+ .map.call(stack, x => ("in " +
543
+ ( (typeof x.entity==='string'? x.entity:x.entity?.name) ||
544
+ x.component?.name ||
545
+ x.constructor?.name
546
+ ) ?? 'undefined') )
547
+ .join('\n');
548
+
549
+ c.component.componentDidCatch(e, {componentStack: stack, componentStackLog: stack_log});
550
+ }
551
+ else throw(e);
552
+
553
+ }
554
+
555
+
556
+ //////////
557
+
558
+
559
+
560
+ function constructFunc(core, parent) // returns {text} or component, and not component core.
561
+ {
562
+ let comp = core;
563
+
564
+ if( core[TEXT]!==undefined ) {
565
+ // do nothing...
566
+ }
567
+ else {
568
+ if(typeof(core.entity)==='string') {
569
+ comp = new HTMLComponent(core.entity, core.props);
570
+ }
571
+ else {
572
+
573
+ if( Lilact.isClass(core.entity) ) {
574
+ if(core.entity?.defaultProps) {
575
+ core.props = { ...core.entity.defaultProps, ...core.props };
576
+ }
577
+
578
+ comp = new core.entity(core.props);
579
+
580
+ const desc = Object.getOwnPropertyDescriptor(comp, "state");
581
+ if(desc) {
582
+ if (typeof desc.get !== "function" && typeof desc.set !== "function") {
583
+ comp[CORE].state = comp.state;
584
+
585
+ Object.defineProperty(comp, "state", {
586
+ get() { return this[CORE].state },
587
+ set(v) {
588
+ // todo: this should be changed, it should be only directly settable in constructor.
589
+ if(this[CORE].state===undefined) {
590
+ this[CORE].state = v;
591
+ }
592
+ else {
593
+ throw 'assigning component state this way is not allowed.';
594
+ }
595
+ }
596
+ });
597
+ }
598
+ }
599
+ }
600
+ else if(typeof(core.entity)==='function') {
601
+
602
+ if(core.entity?.defaultProps) {
603
+ core.props = { ...core.entity.defaultProps, ...core.props };
604
+ }
605
+
606
+ comp = new Component(core.props);
607
+
608
+ // the binding is not necessary and is not according to the specs,
609
+ // probably not even recommended! but helpful.
610
+ comp.render = core.entity.bind(comp);
611
+ comp[CORE].hooks = [];
612
+ comp[CORE].hook_index = 0;
613
+ }
614
+ else {
615
+ ʔ if(DEBUG) {
616
+ console.error(core);
617
+ ʔ }
618
+ throw "createComponent accepts a component class or a function or undefined for the first argument.";
619
+ }
620
+
621
+ comp[CORE].entity = core.entity;
622
+
623
+ if(core.container) {
624
+ comp[CORE].container = core.container;
625
+ }
626
+ }
627
+
628
+ }
629
+
630
+ if(parent instanceof ComponentCore) comp[CORE].parent = parent;
631
+
632
+ return comp;
633
+ }
634
+
635
+
636
+ function prepareCore(parent, core)
637
+ {
638
+ try {
639
+ parent.cache ??= new ComponentCache(parent);
640
+ core = parent.cache.pick( core[TEXT]===undefined?core?.props?.key:':text',
641
+ ()=>( (core[TEXT]!==undefined || core instanceof ComponentCore) ?
642
+ core : constructFunc(core, parent)[CORE] )
643
+ );
644
+ return core;
645
+ }
646
+ catch(e) {
647
+ if(core?.component?.componentDidCatch) {
648
+ core.component.componentDidCatch(e);
649
+ }
650
+ else throw(e);
651
+ }
652
+ }
653
+
654
+
655
+ function doUpdates()
656
+ {
657
+ requestAnimationFrame(()=>{
658
+ let layout_effects = Lilact.layout_effects;
659
+ let update_cbs = Lilact.update_cbs;
660
+ let update_set = Lilact.update_set;
661
+
662
+ Lilact.layout_effects = new Set;
663
+ Lilact.update_cbs = new Set;
664
+ Lilact.update_set = new Set;
665
+
666
+ for(const le of layout_effects) le();
667
+
668
+ for(const u of update_set) u.apply();
669
+ for(const cb of update_cbs) cb();
670
+
671
+ });
672
+ }
673
+
674
+
675
+ function decode(html)
676
+ {
677
+ decode.parser ??= new DOMParser;
678
+ return decode.parser.parseFromString(html, 'text/html').body.textContent;
679
+ }
680
+
681
+
682
+ function escapeHtml(str) {
683
+ escapeHtml.div ??= document.createElement('div');
684
+ div.textContent = String(str);
685
+ return div.innerHTML;
686
+ }
687
+
688
+ const generateComponentKey = (entity, props)=> {
689
+ let key;
690
+
691
+ if(props.key!==undefined) {
692
+ key = /*':k:'+*/ props.key;
693
+ }
694
+ else if(props.id!==undefined) {
695
+ key = ':i:'+props.id;
696
+ }
697
+ else if(props.path!==undefined) {
698
+ key = ':p:'+props.path;
699
+ }
700
+ else if(props[TEXT]!==undefined) {
701
+ key = ':text';
702
+ }
703
+ else {
704
+
705
+ if(typeof(entity)==='string') {
706
+ key = ':t:'+entity;
707
+ }
708
+ else if(!entity) {
709
+ key = "::";
710
+ }
711
+ else {
712
+ key = entity.name;
713
+ }
714
+
715
+ if(props.name!==undefined) {
716
+ key = key+":"+props.name;
717
+ }
718
+ else if(props.path!==undefined) {
719
+ key = key+":"+props.path;
720
+ }
721
+ // else if(props.className!==undefined) {
722
+ // key = key+"."+props.className;
723
+ // }
724
+ }
725
+
726
+ return key;
727
+ }
728
+
729
+
730
+
731
+
732
+ // API
733
+
734
+
735
+
736
+ /**
737
+ * @class
738
+ * Base class that mimics `React.Component` (stateful component with lifecycle hooks).
739
+ * Extend this class to implement `render()` and (optionally) override lifecycle methods.
740
+ *
741
+ * This user functions and members are supported:
742
+ *
743
+ * static defaultProps
744
+ *
745
+ * render() {}
746
+ *
747
+ * componentWillReceiveProps (nextProps)
748
+ * componentWillUpdate (nextProps, nextState)
749
+ * componentDidCatch (error, info)
750
+ * componentDidMount ()
751
+ * componentDidUpdate (prevProps, prevState, lastSnapshot)
752
+ * componentWillUnmount ()
753
+ * getSnapshotBeforeUpdate (prevProps, prevState)
754
+ * shouldComponentUpdate (nextProps, nextState)
755
+ *
756
+ * static getDerivedStateFromError (error) {}
757
+ * static getDerivedStateFromProps (props, state) {}
758
+ *
759
+ * For more details see official React documentation.
760
+ */
761
+ export class Component
762
+ {
763
+
764
+ /**
765
+ * Component state used to drive rendering.
766
+ * Update it with `setState()` to trigger a re-render.
767
+ * @type {object}
768
+ */
769
+ get state() { return this[CORE].state }
770
+ set state(v) {
771
+ // todo: this should be changed, it should be only directly settable in constructor.
772
+ if(this[CORE].state===undefined) {
773
+ this[CORE].state = v;
774
+ }
775
+ else {
776
+ throw 'assigning component state this way is not allowed.';
777
+ }
778
+ }
779
+
780
+ /**
781
+ * Component context.
782
+ * @type {any}
783
+ * @protected
784
+ */
785
+ get context() { return this[CORE].context }
786
+ set context(v) { throw 'assigning component context this way is not allowed.' }
787
+
788
+ /**
789
+ * Component context value.
790
+ * Use it to access shared data provided by an outer component/system.
791
+ * @type {any}
792
+ */
793
+ get type() { return this[CORE].entity }
794
+ set type(v) { throw 'component type is immutable.' }
795
+
796
+ /**
797
+ * Props passed into the component instance.
798
+ * Use it as read-only input when rendering.
799
+ * @type {any}
800
+ */
801
+ get props() { return this[CORE].props }
802
+ set props(v) { throw 'assigning component props this way is not allowed.' }
803
+
804
+ /**
805
+ * A reference associated with the component to be used with useRef.
806
+ * Can be used to expose the component instance or an underlying DOM node.
807
+ * @type {any}
808
+ */
809
+ get ref() { return this[CORE].ref }
810
+ set ref(v) { throw 'component ref is immutable.' }
811
+
812
+ /**
813
+ * A unique identifier for the component instance.
814
+ * The key is immutable and can only be set when the component is declared.
815
+ * @type {string|number}
816
+ */
817
+ get key() { return this[CORE].props.key }
818
+ set key(v) { throw 'component key is immutable.' }
819
+
820
+ constructor(props)
821
+ {
822
+ this[CORE] = new ComponentCore(this, props);
823
+ }
824
+
825
+ /**
826
+ * Force the component to re-render even if no state/props change.
827
+ * Useful for imperative updates.
828
+ * @returns {void}
829
+ */
830
+ forceUpdate(callback)
831
+ {
832
+ Lilact.clearTimeout(Lilact.update_timeout);
833
+
834
+ Lilact.update_set.add(this[CORE].container || this[CORE]);
835
+ if(callback) Lilact.update_cbs.add(callback);
836
+ Lilact.update_timeout = Lilact.setTimeout( doUpdates, Lilact.update_interval_margin );
837
+ }
838
+
839
+ /**
840
+ * Update component state.
841
+ * Accepts a partial state (or a function returning partial state) and schedules a re-render.
842
+ * @param {any} new state
843
+ * @returns {void}
844
+ */
845
+ setState(next_state, callback)
846
+ {
847
+ if(this.getSnapshotBeforeUpdate!==undefined) {
848
+ this[CORE].last_snapshot = this.getSnapshotBeforeUpdate(this[CORE].props, this.state);
849
+ }
850
+
851
+ this[CORE].next_state = next_state;
852
+ this.forceUpdate(callback?callback.bind(this):undefined);
853
+ }
854
+
855
+ /* User Functions
856
+
857
+ static defaultProps
858
+
859
+ render () {}
860
+
861
+ componentWillReceiveProps (next_props)
862
+ componentWillUpdate (next_props, next_state)
863
+ componentDidCatch (error, info) {}
864
+ componentDidMount () {}
865
+ componentDidUpdate (prevProps, prevState, last_snapshot) {}
866
+ componentWillUnmount () {}
867
+ getSnapshotBeforeUpdate (prevProps, prevState) {}
868
+ shouldComponentUpdate (nextProps, nextState) {}
869
+
870
+ static getDerivedStateFromError (error) {}
871
+ static getDerivedStateFromProps (props, state) {}
872
+
873
+
874
+ */
875
+ /* // todo: maybe
876
+ static get contextType() { }
877
+ static set contextType(ctxt) { }
878
+
879
+ static get childContextTypes() {}
880
+ static set childContextTypes(ctxt) { }
881
+
882
+ getChildContext()
883
+ */
884
+ }
885
+
886
+ /**
887
+ * @class HTMLComponent
888
+ * @extends Component
889
+ *
890
+ * Lightweight React-like component that creates and manages a single HTML element.
891
+ * It renders an HTML element of the given tag/type (`entity`) and applies the provided `props`.
892
+ *
893
+ * @example
894
+ * <div {...props}>...</div>
895
+ * or
896
+ * const el = new HTMLComponent('div', { className: 'box' });
897
+ *
898
+ * @param {string} entity - The HTML tag/type to create (e.g., 'div', 'span', 'button').
899
+ * @param {Object} props - Props to apply to the created element.
900
+ */
901
+
902
+ export class HTMLComponent extends Component
903
+ {
904
+ constructor(entity, props)
905
+ {
906
+ super(props);
907
+ this[CORE].entity = entity;
908
+ }
909
+
910
+ render()
911
+ {
912
+ return this[CORE].props.children;
913
+ }
914
+ }
915
+
916
+
917
+ /**
918
+ * @class RootComponent
919
+ * @extends HTMLComponent
920
+ *
921
+ * Root-level component that receives a pre-existing root HTML element and builds/receives its children using `props`.
922
+ * It uses `props.children` (and related conventions) as the primary input for what to render inside the root.
923
+ *
924
+ * @example
925
+ * // Accept an element reference
926
+ * const root = document.getElementById('app')
927
+ * const app = new RootComponent(root, { children: [...] })
928
+ *
929
+ * // Or accept a selector string
930
+ * const app2 = new RootComponent('#app', { children: [...] })
931
+ *
932
+ * @param {HTMLElement|string} rootElement - Root HTML element (or a selector string resolved via `document.querySelector`).
933
+ * @param {Object} props - Root props used to configure how children are provided and attached (typically includes `props.children`).
934
+ *
935
+ * @property {HTMLElement|string} rootElement - The root element reference (or tag/type/selector depending on how you pass it in).
936
+ * @property {Object} props - Root props used to build/receive children.
937
+ */
938
+
939
+ export class RootComponent extends HTMLComponent
940
+ {
941
+ constructor(element, props)
942
+ {
943
+ super(':root', props);
944
+
945
+ if(typeof this.element==='string') {
946
+ element = document.querySelector(element);
947
+ }
948
+
949
+ this[CORE].element = element;
950
+
951
+ for(const ch of props.children) {
952
+ if(ch[CORE]) ch[CORE].container = this[CORE];
953
+ else ch.container = this[CORE];
954
+ }
955
+ }
956
+ }
957
+
958
+
959
+ /**
960
+ * Creates an HTML/React-like component instance.
961
+ * This is what the JSX transpiler uses internally for `<Component>...</Component>`-style expressions.
962
+ * It is also aliased to `createElement` for compatibility with the React API.
963
+ *
964
+ * @param {string} entity - The HTML tag/type to create (e.g., 'div', 'span', 'button').
965
+ * @param {Object} [props={}] - Props/attributes to apply to the created element.
966
+ * @param {...any} children - Child nodes or values to attach (e.g., strings, HTMLElements, component instances, or arrays).
967
+ *
968
+ * @returns {HTMLComponent} The created component instance.
969
+ */
970
+
971
+ export function createComponent(entity, props={}, ...children)
972
+ {
973
+
974
+ for(let i=0; i<children.length; i++) {
975
+ let ch = children[i];
976
+
977
+ if(ch===undefined || ch===null || typeof(ch)==='boolean') {
978
+ children.splice(i, 1);
979
+ i--;
980
+ continue;
981
+ }
982
+
983
+ if( ["number", "bigint"].indexOf(typeof(ch))!==-1 ) {
984
+ ch = ch.toString();
985
+ }
986
+
987
+ if( typeof(ch)==='string' ) {
988
+ children[i] = { [TEXT]: ch };
989
+ }
990
+ else {
991
+ children[i] = ch;
992
+ }
993
+ }
994
+
995
+ if(entity===null) return children; // <> style fragment
996
+
997
+ props.key = generateComponentKey(entity, props);
998
+ props.children = children;
999
+
1000
+ return { entity, props };
1001
+ }
1002
+
1003
+ /**
1004
+ * Creates a root controller bound to a specific DOM element.
1005
+ * The returned object manages mounting/updating and removal of component trees.
1006
+ *
1007
+ * @param {HTMLElement|string} element
1008
+ * Root HTML element to use. If a string is provided, it is resolved via `document.querySelector`.
1009
+ *
1010
+ * @returns {Object} Root controller.
1011
+ * @returns {Object.render} controller.render(component)
1012
+ * Mounts (or updates) the provided component into the root element.
1013
+ * @returns {Object.unmount} controller.unmount()
1014
+ * Removes/unmounts the currently rendered component tree from the root element.
1015
+ */
1016
+
1017
+ export function createRoot(element)
1018
+ {
1019
+ let root;
1020
+
1021
+ return {
1022
+ render(component) {
1023
+ if(!root) {
1024
+ root = new RootComponent( element, {children:[component]} );
1025
+ Lilact.roots.add(root[CORE]);
1026
+ root.forceUpdate();
1027
+ return root;
1028
+ }
1029
+ else {
1030
+ throw "root already initialized!";
1031
+ }
1032
+ },
1033
+
1034
+ unmount() {
1035
+ if(root) {
1036
+ root.cleanup();
1037
+ element.innerHTML="";
1038
+ }
1039
+ }
1040
+ }
1041
+ }
1042
+
1043
+ /**
1044
+ * Renders a component into a target DOM element.
1045
+ * If the component maintains internal state, this typically mounts it (or updates the existing tree) under `element`.
1046
+ *
1047
+ * @param {Object} component - Component instance to render.
1048
+ * @param {HTMLElement|string} element
1049
+ * Target element to render into. If a string is provided, it is resolved via `document.querySelector`.
1050
+ *
1051
+ * @returns {void}
1052
+ */
1053
+
1054
+ export function render(component, element)
1055
+ {
1056
+ if(component[CORE] && (component[CORE].container || component[CORE].parent)) {
1057
+ throw "component is already in use";
1058
+ }
1059
+ return createRoot(element).render(component);
1060
+ }
1061
+
1062
+ /** @ignore */
1063
+ export const createElement = createComponent;