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.
- package/Icon/r +0 -0
- package/LICENSE.TXT +26 -0
- package/README.md +116 -0
- package/bin/copy-demo.js +26 -0
- package/bin/transpile-dir.js +112 -0
- package/bin/transpile.js +137 -0
- package/dist/lilact.development.js +9344 -0
- package/dist/lilact.development.js.map +1 -0
- package/dist/lilact.production.min.js +63 -0
- package/dist/lilact.production.min.js.map +1 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/highlight.css +71 -0
- package/docs/assets/icons.js +18 -0
- package/docs/assets/icons.svg +1 -0
- package/docs/assets/main.js +60 -0
- package/docs/assets/navigation.js +1 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1633 -0
- package/docs/classes/accessories.ErrorBoundary.html +37 -0
- package/docs/classes/accessories.Suspense.html +41 -0
- package/docs/classes/components.Component.html +45 -0
- package/docs/classes/components.HTMLComponent.html +32 -0
- package/docs/classes/components.RootComponent.html +32 -0
- package/docs/classes/transition.Transition.html +31 -0
- package/docs/functions/accessories.Spinner.html +9 -0
- package/docs/functions/components.createComponent.html +8 -0
- package/docs/functions/components.createRoot.html +5 -0
- package/docs/functions/components.render.html +5 -0
- package/docs/functions/hooks.createContext.html +3 -0
- package/docs/functions/hooks.useActionState.html +3 -0
- package/docs/functions/hooks.useCallback.html +5 -0
- package/docs/functions/hooks.useContext.html +3 -0
- package/docs/functions/hooks.useDeferredValue.html +4 -0
- package/docs/functions/hooks.useEffect.html +3 -0
- package/docs/functions/hooks.useHook.html +5 -0
- package/docs/functions/hooks.useId.html +4 -0
- package/docs/functions/hooks.useLayoutEffect.html +3 -0
- package/docs/functions/hooks.useLocalStorage.html +4 -0
- package/docs/functions/hooks.useMemo.html +4 -0
- package/docs/functions/hooks.useReducer.html +6 -0
- package/docs/functions/hooks.useRef.html +4 -0
- package/docs/functions/hooks.useState.html +3 -0
- package/docs/functions/hooks.useTransition.html +3 -0
- package/docs/functions/jsx.transpileJSX.html +6 -0
- package/docs/functions/misc.Fragment.html +3 -0
- package/docs/functions/misc.classNames.html +2 -0
- package/docs/functions/misc.deepEqual.html +3 -0
- package/docs/functions/misc.findDOMNode.html +3 -0
- package/docs/functions/misc.getComponentByPointer.html +3 -0
- package/docs/functions/misc.isAsync.html +3 -0
- package/docs/functions/misc.isClass.html +3 -0
- package/docs/functions/misc.isEmpty.html +3 -0
- package/docs/functions/misc.isError.html +3 -0
- package/docs/functions/misc.isThenable.html +3 -0
- package/docs/functions/misc.isValidElement.html +3 -0
- package/docs/functions/misc.shallowEqual.html +3 -0
- package/docs/functions/redux.Provider.html +5 -0
- package/docs/functions/redux.connect.html +5 -0
- package/docs/functions/redux.useDispatch.html +3 -0
- package/docs/functions/redux.useSelector.html +5 -0
- package/docs/functions/redux.useStore.html +3 -0
- package/docs/functions/router.HashRouter.html +5 -0
- package/docs/functions/router.Link.html +12 -0
- package/docs/functions/router.NavLink.html +10 -0
- package/docs/functions/router.Route.html +6 -0
- package/docs/functions/router.Routes.html +4 -0
- package/docs/functions/router.useLocation.html +3 -0
- package/docs/functions/router.useNavigate.html +3 -0
- package/docs/functions/run.require.html +14 -0
- package/docs/functions/run.run.html +13 -0
- package/docs/functions/run.runScripts.html +7 -0
- package/docs/functions/run.traceError.html +6 -0
- package/docs/functions/timers.animationFramePromise.html +3 -0
- package/docs/functions/timers.clearInterval.html +2 -0
- package/docs/functions/timers.clearTimeout.html +2 -0
- package/docs/functions/timers.grabTimers.html +3 -0
- package/docs/functions/timers.pauseTimers.html +2 -0
- package/docs/functions/timers.releaseTimers.html +2 -0
- package/docs/functions/timers.resetTimers.html +2 -0
- package/docs/functions/timers.resumeTimers.html +2 -0
- package/docs/functions/timers.setInterval.html +5 -0
- package/docs/functions/timers.setTimeout.html +4 -0
- package/docs/functions/timers.timeoutPromise.html +4 -0
- package/docs/functions/transition.CSSTransition.html +18 -0
- package/docs/functions/transition.TransitionGroup.html +5 -0
- package/docs/hierarchy.html +1 -0
- package/docs/index.html +88 -0
- package/docs/media/icon.png +0 -0
- package/docs/modules/accessories.html +1 -0
- package/docs/modules/components.html +1 -0
- package/docs/modules/hooks.html +1 -0
- package/docs/modules/jsx.html +1 -0
- package/docs/modules/misc.html +1 -0
- package/docs/modules/redux.html +1 -0
- package/docs/modules/router.html +1 -0
- package/docs/modules/run.html +1 -0
- package/docs/modules/timers.html +1 -0
- package/docs/modules/transition.html +1 -0
- package/docs/modules.html +1 -0
- package/docs/static/demos/actionstate.jsx +41 -0
- package/docs/static/demos/boundary.jsx +52 -0
- package/docs/static/demos/context.jsx +68 -0
- package/docs/static/demos/css-transition.jsx +65 -0
- package/docs/static/demos/modal.jsx +41 -0
- package/docs/static/demos/proptypes.jsx +37 -0
- package/docs/static/demos/reducer.jsx +71 -0
- package/docs/static/demos/redux.jsx +58 -0
- package/docs/static/demos/router.jsx +153 -0
- package/docs/static/demos/stopwatch.jsx +42 -0
- package/docs/static/demos/suspense.jsx +48 -0
- package/docs/static/demos/transition.jsx +40 -0
- package/docs/static/demos/use-differed.jsx +30 -0
- package/docs/static/demos/usetransition.jsx +37 -0
- package/docs/static/index.html +212 -0
- package/docs/static/lilact.development.js +9344 -0
- package/docs/static/lilact.production.min.js +63 -0
- package/docs/static/prismjs/LICENSE.md +21 -0
- package/docs/static/prismjs/prism-jsx.min.js +1 -0
- package/docs/static/prismjs/prism.min.css +1 -0
- package/docs/static/prismjs/prism.min.js +16 -0
- package/docs/variables/jsx.transpilerConfig.html +1 -0
- package/docs/variables/misc.Children.html +10 -0
- package/icon.png +0 -0
- package/package.json +39 -0
- package/root/demos/actionstate.jsx +41 -0
- package/root/demos/boundary.jsx +52 -0
- package/root/demos/context.jsx +68 -0
- package/root/demos/css-transition.jsx +65 -0
- package/root/demos/modal.jsx +41 -0
- package/root/demos/proptypes.jsx +37 -0
- package/root/demos/reducer.jsx +71 -0
- package/root/demos/redux.jsx +58 -0
- package/root/demos/router.jsx +153 -0
- package/root/demos/stopwatch.jsx +42 -0
- package/root/demos/suspense.jsx +48 -0
- package/root/demos/transition.jsx +40 -0
- package/root/demos/use-differed.jsx +30 -0
- package/root/demos/usetransition.jsx +37 -0
- package/root/index.html +212 -0
- package/root/lilact.development.js +9344 -0
- package/root/lilact.production.min.js +63 -0
- package/root/prismjs/LICENSE.md +21 -0
- package/root/prismjs/prism-jsx.min.js +1 -0
- package/root/prismjs/prism.min.css +1 -0
- package/root/prismjs/prism.min.js +16 -0
- package/src/accessories.jsx +287 -0
- package/src/components.jsx +1063 -0
- package/src/events.jsx +175 -0
- package/src/hooks.jsx +461 -0
- package/src/jsx.addons.js +70 -0
- package/src/jsx.js +1168 -0
- package/src/lilact.jsx +115 -0
- package/src/loader.cjs +59 -0
- package/src/misc.jsx +419 -0
- package/src/redux.jsx +198 -0
- package/src/router.jsx +282 -0
- package/src/run.jsx +275 -0
- package/src/timers.jsx +313 -0
- package/src/transition.jsx +244 -0
- package/src/vlq.js +94 -0
- package/tsconfig.json +15 -0
- package/typedoc.json +23 -0
- 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;
|